2023-02-03         List and lists

 I stumbled  across this text  adventure called "Lists  and Lists"[1]
 for  the Z-machine[2]  by Andrew  Plotkin  where a  genie gives  you
 coding problems  and you  have to  solve them  in a  very restricted
 Scheme environment. The  genie then checks your  progress by running
 some tests with  random inputs. It was a fun  challenge so I decided
 to document  my solutions.  The text  adventure part  is practically
 non-existent.  When you  have gained  acces to  the computer  (after
 opening the  door, smashing the  glass box and answering  "yes") the
 game-loop looks this:

   turn on computer
   >>'(write scheme code)
   :q
   check

 There is a manual included which is very helpful because it's really
 a bare-bones Scheme implementation which even lacks a multiplication
 function. Of  course I wrote most  of the solutions in  Emacs. I had
 the game running in an *ansi-term*  buffer and simply pasted my code
 into  it  (C-c  C-j  enters the  `term-line-mode`  where  all  Emacs
 keybindings are present). Here it goes:




 ;; GENIE:  Your first  problem  is  just to  acquaint  you with  the
 ;; system. Start up the machine,  and define twentyseven to have the
 ;; value  27. You  can  ask  me to  'check'  when  you're ready,  or
 ;; 'repeat' the problem if you need me to.

 (define twentyseven 27)

 ;; GENIE: Let's try  creating some lists. Define values  for cat and
 ;; dog so  that cat and  dog are  equal? but not  eqv?. Furthermore,
 ;; cdr(cat) and cdr(dog) must be eqv?.

 (define dog '(5))
 (define cat '(5))

 ;; GENIE:  Perfect!  There  are  actually two  ways  to  solve  this
 ;; problem.  You used  the simpler  one, using  one-term lists.  The
 ;; trickier solution would be something like this:

 ;; (define tail '(end))
 ;; (define cat (cons 'head tail))
 ;; (define dog (cons 'head tail))

 ;; The cdrs are eqv? because they  are both the thing defined on the
 ;; first line. See?"

 ;; GENIE: Define abs to be the absolute value function for integers.
 ;; That is, (abs  4) should return 4; (abs -5)  should return 5; and
 ;; (abs 0) should return 0.

 (define abs (lambda (x)
               (cond ((< x 0) (- x))
                     (t x))))

 ;; GENIE:  Define sum  to  be a  function  that adds  up  a list  of
 ;; integers. So (sum '(8 2 3))  should return 13. Make sure it works
 ;; correctly for the empty list; (sum nil) should return 0.

 (define cadr (lambda (x)
                (car (cdr x))))

 (define sum (lambda (ls)
               (cond ((null? ls) 0)
                     ((= (length ls) 2) (+ (car ls) (cadr ls)))
                     (t (+ (car ls) (sum (cdr ls)))))))

 ;; GENIE:  This problem  is like  the  last one,  but more  general.
 ;; Define megasum to add up  an arbitrarily nested list of integers.
 ;; That is, (megasum '((8) 5 (2 () (9 1) 3))) should return 28.

 (define megasum (lambda (ls)
               (cond ((null? ls) 0)
                     ((not (list? (car ls))) (+ (car ls)
                                                (megasum (cdr ls))))
                     (t (+ (megasum (car ls))
                           (megasum (cdr ls)))))))

 ;; GENIE: Define  max to be a  function that finds the  maximum of a
 ;; list of integers.  So (max '(5 14 -3)) should  return 14. You can
 ;; assume the list will have at least one term.

 (define max-rec (lambda args
                  (let ((max-elem (car args))
                        (ls (cadr args)))
                    (cond ((null? ls) max-elem)
                          ((> (car ls) max-elem) (max-rec (car ls) (cdr ls)))
                          (t (max-rec max-elem (cdr ls)))))))

 (define max (lambda (ls) (max-rec (car ls) ls)))

 ;; GENIE: Last  problem. You're  going to  define a  function called
 ;; pocket. This function should take one argument. Now pay attention
 ;; here:  pocket  does  two   different  things,  depending  on  the
 ;; argument. If  you give it nil  as the argument, it  should simply
 ;; return 8. But  if you give pocket any integer  as an argument, it
 ;; should  return a  new pocket  function  -- a  function just  like
 ;; pocket, but with that new integer hidden inside, replacing the 8.

 ;;  >>(pocket nil)
 ;;  8
 ;;  >>(pocket 12)
 ;;  [function]
 ;;  >>(define newpocket (pocket 12))
 ;;  [function]
 ;;  >>(newpocket nil)
 ;;  12
 ;;  >>(define thirdpocket (newpocket 3))
 ;;  [function]
 ;;  >>(thirdpocket nil)
 ;;  3
 ;;  >>(newpocket nil)
 ;;  12
 ;;  >>(pocket nil)
 ;;  8

 ;; Note that when you create a new pocket function,
 ;; previously-existing functions should keep working.

 (define pocket-gen (lambda (x)
                  (letrec
                      ((f (lambda (y)
                            (cond ((null? y) x)
                                  (t (pocket-gen y))))))
                    f)))

 (define pocket (lambda (a)
                  (cond ((null? a) 8)
                        (t (pocket-gen a)))))




 APPENDIX

 My  first solution  to `megasum`  actually looked  different. I  was
 convinced that I  had to use `sum` from the  *last* problem to solve
 *this* problem so  I had to define some common  scheme functions not
 present in  the environment.  All the  time I  was thinking  that my
 solution was over-engineered  and sure it was.  After thinking about
 it for a few days I just modified `sum` to work for nested lists.

 (define append (lambda args
                  (let ((ls (car args))
                        (tail (cadr args)))
                    (cond ((null? ls) tail)
                          (t (cons (car ls)
                                   (append (cdr ls) tail)))))))

 (define flatten (lambda (ls)
                   (cond ((null? ls) '())
                         ((list? (car ls)) (append (flatten (car ls))
                                                   (flatten (cdr ls))))
                         (t (cons (car ls)
                                  (flatten (cdr ls)))))))

 (define megasum (lambda (ls) (sum (flatten ls))))


 The final  problem actually took me  a while to understand.  I tried
 many approaches but  always ended up where I began.  Then I actually
 read the included manual page about `letrec` and came quite close to
 the solution but the tests still failed. I was a bit frustrated so I
 wrote  this brain-dead  version of  the pocket  function which  only
 works for  three nesting-levels. Since  the genie doesn't  check any
 further  I finally  beat the  "game"  but of  course I  was not  yet
 satisfied. So a few days later I'm laying on the couch as I get this
 funny  idea.  I pull  out  my  laptop, make  a  final  change to  my
 `letrec`-version  of the  pocket  function and,  lo  and behold,  it
 works. XD

 (define pocket (lambda (a)
                  (cond ((null? a) 8)
                        (t (lambda (b)
                             (cond ((null? b) a)
                                   (t (lambda (c)
                                        (cond ((null? c) b)
                                              (t 'fuck))))))))))


Footnotes
_________

[1] https://ifdb.org/viewgame?id=zj3ie12ewi1mrj1t

[2]  https://en.wikipedia.org/wiki/Z-machine
    I used the program  `frotz`  from the Ubuntu/Debian repository
    to run the game