CSCI 330 Lab 2: higher order functions, let-over-(labels-over-)lambda

Continuing with our use of sbcl, this time we'll focus on the use of higher order functions in lisp and the let-over-lambda construct.

You'll have two weeks to do the lab, starting with the lab session on Jan. 27, and it will be due at the end of the day on Feb. 9th.

Note: the lab session of Feb. 3rd will be used for the first in-lab quiz of the term.

Obtaining and submitting the lab will be done using the same mechansim as in lab 1, though of course this time the repo name is lab2.

Instructions are given below and will be discussed in the lab session, along with further discussions of higher order functions and let-over-lambda.

Marking: the lab will be marked out of 21. Of the 21 marks: 7 marks are for the useEach function in lab2a_fun.cl and 14 marks are for the implementation of the buildTree construction function and its three specified local methods.


Lab 2 objectives

Lab 2 is meant to give you practice with higher order functions and let-over-lambda in lisp:


Lab 2 requirements and restrictions

For lab 2, you are to finish implementing the functions in the provided lab2a_fun.cl and lab2b_fun.cl files and test/debug them using the provided lab2a_test.cl and lab2b_test.cl scripts. (When I mark your lab2a/b_fun.cl code I'll be using my own test script that loads the *fun.cl files and tests the functions work as specified independently.)

General/structural requirements:


Part I: lab2a_fun.cl and lab2a_test.cl

Copies of the starting contents for the lab2a_fun and lab2a_test files are provided below, along with some suggestions for implementing useEach.

; (useEach FL AL)
; ---------------
; applies each function in list FL to the set of parameters in list AL,
;    e.g. (useEach '(* + >) '(5 4))
;         builds and returns a list from the results of
;           (* 5 4)  (+ 5 4)  (< 5 4)
; generates an error message if FL or AL are  not lists
; generates an error message and skips anything in FL that
;    is not a symbol bound to a function (i.e. checks both symbolp and fboundp for each)
;    but it does continue processing the rest of the functions in FL
(defun useEach (L arglist)
   nil)

Implementation suggestions:

The test script and a sample working run:

#! /usr/bin/sbcl --script

(format t "~%--- Begin loading lab2a_fun.cl ---~%")
(load "lab2a_fun.cl")
(format t "~%--- completed loading of lab2a_fun.cl ---~%")

; ------------ testing of useEach --------------------

(format t "~%---------- Testing useEach ----------~%")

(defun prt1 (x) (format nil "~A" x))
(defun prt2 (x y) (format nil "~A,~A" x y))

(defvar A1 '(1.21))
(defvar L1 (list 'sqrt 3 'prt1))

(defvar A2 '(5 (10 15)))
(defvar L2 (list 'prt2 'cons 'foo))

(defvar A3 '(2 6))
(defvar L3 (list '< '* '+))

(defvar A4 '(2.3 1.6))
(defvar L4 '(> / -))

(defun testUE (L A)
   (format t "~%Testing useEach on funlist ~A applied to args ~A~%" L A)
   (format t "Final results list was ~A~%" (useEach L A)))

(testUE L1 A1)
(testUE L2 A2)
(testUE L3 A3)
(testUE L4 A4)
(testUE nil A1)
(testUE 5 A2)

(format t "~%---------- Testing finished  ----------~%~%")

--- Begin loading lab2a_fun.cl --- --- completed loading of lab2a_fun.cl --- ---------- Testing useEach ---------- Testing useEach on funlist (SQRT 3 PRT1) applied to args (1.21) Skipping 3, not a symbol Final results list was (1.1 1.21) Testing useEach on funlist (PRT2 CONS FOO) applied to args (5 (10 15)) Skipping FOO, not bound to a function Final results list was (5,(10 15) (5 10 15)) Testing useEach on funlist (< * +) applied to args (2 6) Final results list was (T 12 8) Testing useEach on funlist (> / -) applied to args (2.3 1.6) Final results list was (T 1.4375 0.6999999) Testing useEach on funlist NIL applied to args (1.21) Final results list was NIL Testing useEach on funlist 5 applied to args (5 (10 15)) List of functions expected, got 5 Final results list was NIL ---------- Testing finished ----------


Part II: lab2b_fun.cl and lab2b_test.cl

buildTree works on a binary tree of key-value pairs represented using a list structure, where nil represents an empty tree and otherwise a tree is composed of four elements: the key and data values for the root of the tree (or subtree) then the list representing the left subtree, then the list representing the right subtree.

Thus a tree consisting of just the single key-value pair 3,17 would look like
(3 17 nil nil).
If we were to give that a left subtree whose key/value were 1,200 then the new tree would look like
(3 17 (1 200 nil nil) nil)
If we gave the root a right subtree with key/value 99,1000 then the result would be
(3 17 (1 200 nil nil) (99 1000 nil nil))
etc.

Note the format is not terribly readable for humans, but works out well for recursive processing.

buildTree expects to be passed the tree and the data types for its keys and values, e.g.
(buildtree (3 17 (1 200 nil nil) (99 1000 nil nil)) 'integer 'integer)

Using the let-over-lambda approach discussed in lectures, it returns a dispatch function that allows us to issue commands and options to process the tree where the commands are 'print or 'reset and the options are 'inorder, 'preorder, or 'postorder:

(defvar tr1 (buildtree (3 17 (1 200 nil nil) (99 1000 nil nil)) 'integer 'integer))
(funcall tr 'print 'postorder)
(funcall tr 'reset 'inorder)

Copies of the starting contents for the lab2b_fun and lab2b_test files are provided below, along with implementation suggestions for the methods of buildTree.

; (printNode n)
; print the given node's key/value: assumes n is structurally valid
; (i.e trusts n is either nil or a list whose front two elements are a key and value)
(defun printNode (n)
   (when (not (null n))
      (format t "(~A:~A)" (car n) (cadr n))))

; (resetNode n)
; resets the node's value field to nil: assumes n is structurally valid
; (i.e trusts n is either nil or a list whose front two elements are a key and value)
(defun resetNode (n)
   (when (not (null n))
      (setf (cadr n) nil)))

; (buildtree inittree k v)
; ------------------------
; builds and returns a dispatcher to handle commands to apply to a binary tree
;    whose content is provided in inittree
;       (optional arg: nil if not provided)
;    and whose key and value data types are k and v respectively
;       (also optional args: 'integer if not provided)
; the supported dispatcher commands are provided in the lambda section,
;     and the supported internal methods are described in the labels section
(defun buildtree (&optional (inittree nil) (k 'integer) (v 'integer))
   (let  ((root inittree) ; root of tree
          (keytype (if (symbolp k) k 'integer))  ; expected data type for node keys
          (valtype (if (symbolp v) v 'integer))) ; expected data type for node values

         (labels ( ; local methods

            ; (setTypes k v)
            ; set the expected key and value types for tree node contents
            ;     no restrictons on k/v other than being symbols,
            ;     e.g. (setTypes 'integer 'real)
            (setTypes (k v)
                nil)

            ; (checkNode n quiet)
            ; check if node n has valid structure and key/value types:
            ;    - a list of 4 fields of appropriate types:
            ;      1st of type keytype, 2nd of type valtype, 3rd and 4th are lists
            ;    - is *not* expected to recursively check third and fourth fields,
            ;      just check that they are lists
            ;    prints error message and returns nil if error detected,
            ;      otherwise returns t
            (checkNode (n)
                nil)

             ; (traverse n order action)
             ; traverse subtree n using the indicated order (preorder, inorder, postorder)
             ;    and on each subtree applies the indicated action (printNode or resetNode)
             (traverse (n order action)
                 nil)

            ) ; end of local methods

         (lambda (cmd &optional (arg1 nil) (arg2 nil))
            (cond
               ; command was: setTypes keytype valtype
               ((equalp cmd 'setTypes) (setTypes arg1 arg2))

               ; command was: reset traversal-order value
               ((equalp cmd 'reset) (traverse root arg1 'resetNode))

               ; command was: print traversal-order
               ((equalp cmd 'print) (traverse root arg1 'printNode))

               (t (format t "Error: invalid command ~A~%" cmd)))))))

Implementation suggestions:

The test script and a sample working run:
#! /usr/bin/sbcl --script

(format t "~%--- Begin loading lab2b_fun.cl ---~%")
(load "lab2b_fun.cl")
(format t "~%--- completed loading of lab2b_fun.cl ---~%")

; ---------- testing of myfun function ---------------------------------
(defun testTree (tree ktype vtype)
    (let ((dispatch nil))
       (format t "Building dispatcher~%")
           (format t "enter anything to continue~%")(read-line)
       (setf dispatch (buildTree tree))
       (when (null dispatch)
           (format t "Null dispatcher created~%"))
       (unless (null dispatch)
           (format t "~%setting key/value expected types (~A,~A)~%" ktype vtype)
           (funcall dispatch 'setTypes ktype vtype)

           (format t "~%running pre-order print~%")
           (funcall dispatch 'print 'preorder)
           (format t "~%~%running in-order print~%")
           (funcall dispatch 'print 'inorder)
           (format t "~%~%running post-order print~%")
           (funcall dispatch 'print 'postorder)

           (format t "~%~%reset default value to nil~%")
           (funcall dispatch 'reset 'preorder)
           (format t "~%running in-order print~%")
           (funcall dispatch 'print 'inorder)
           (format t "~%~%"))))



; ---------- testing of myfun function ---------------------------------

(format t "~%--- run tests on bstree of int/int  ---~%")
(testTree '(4 1 (2 2 (1 3 nil nil) (3 4 nil nil)) (6 5 (5 6 nil nil) (7 7 nil nil))) 'integer 'integer)

(format t "~%--- run tests on tree with types real,real but one invalid key ---~%")
(testTree '(10.1 1 (5.5 2 (0.1 3 nil nil) (6.7 4 nil nil)) ("ick" 5 (50.5 6 nil nil) (700 7 nil nil))) 'real 'real)

(format t "~%--- run tests on tree with types real,integer but one invalid value ---~%")
(testTree '(10.1 1 (5.5 2 (0.1 3 nil nil) (6.7 4 nil nil)) (50 5 (50.5 6 nil nil) (700 7.7777 nil nil))) 'real 'integer)

(format t "~%--- run tests on non-list with types real,real ---~%")
(testTree 10 'real 'real)

(format t "~%---END OF TESTING---~%~%")


--- Begin loading lab2b_fun.cl --- --- completed loading of lab2b_fun.cl --- --- run tests on bstree of int/int --- Building dispatcher enter anything to continue setting key/value expected types (INTEGER,INTEGER) running pre-order print (4:1)(2:2)(1:3)(3:4)(6:5)(5:6)(7:7) running in-order print (1:3)(2:2)(3:4)(4:1)(5:6)(6:5)(7:7) running post-order print (1:3)(3:4)(2:2)(5:6)(7:7)(6:5)(4:1) reset default value to nil running in-order print Error: value field NIL must be type INTEGER --- run tests on tree with types real,real but one invalid key --- Building dispatcher enter anything to continue setting key/value expected types (REAL,REAL) running pre-order print (10.1:1)(5.5:2)(0.1:3)(6.7:4)Error: key field ick must be type REAL running in-order print (0.1:3)(5.5:2)(6.7:4)(10.1:1)Error: key field ick must be type REAL running post-order print (0.1:3)(6.7:4)(5.5:2)Error: key field ick must be type REAL (10.1:1) reset default value to nil Error: key field ick must be type REAL running in-order print Error: value field NIL must be type REAL --- run tests on tree with types real,integer but one invalid value --- Building dispatcher enter anything to continue setting key/value expected types (REAL,INTEGER) running pre-order print (10.1:1)(5.5:2)(0.1:3)(6.7:4)(50:5)(50.5:6)Error: value field 7.7777 must be type INTEGER running in-order print (0.1:3)(5.5:2)(6.7:4)(10.1:1)(50.5:6)(50:5)Error: value field 7.7777 must be type INTEGER running post-order print (0.1:3)(6.7:4)(5.5:2)(50.5:6)Error: value field 7.7777 must be type INTEGER (50:5)(10.1:1) reset default value to nil Error: value field 7.7777 must be type INTEGER running in-order print Error: value field NIL must be type INTEGER --- run tests on non-list with types real,real --- Building dispatcher enter anything to continue setting key/value expected types (REAL,REAL) running pre-order print Error: tree nodes must be lists, got 10 running in-order print Error: tree nodes must be lists, got 10 running post-order print Error: tree nodes must be lists, got 10 reset default value to nil Error: tree nodes must be lists, got 10 running in-order print Error: tree nodes must be lists, got 10 ---END OF TESTING---