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

The lab2 repo has now been pushed and is ready to be forked/cloned.

In this lab we'll refactor the work started in lab 1, introducing let-over-lambda for OO-like control. For this lab feel free to base your solution off your (corrected) lab1, or the posted sample solution, or to work completely from scratch. (I found getting a clean solution was easiest by writing from scratch but with my lab1.cl code handy for the checking logic/syntax.)

For this lab you are permitted to use lisp loops (dolist, do, etc).

You'll have two weeks to do the lab (including the lab sessions on Jan. 29th and any time after the quiz on Feb. 5th), and it will be due at noon on Feb. 12th.

Similar to lab1, the exercise involves forking/cloning a lab2 git repository, editing the lab2.cl and selftest.cl files, and then submitting your updated repository.

The instructions to fork/clone/submit the lab are as per lab1, except that the repo this time is named lab2.


Lab 2 objectives and requirements

Lab 2 is meant to replace the grade-handling functionality with an OO-like structure using let-over-(labels-over)lambda.

Many of the same behavioural rules apply as with lab 1:

For lab 2, however, we'll be using a let-over-lambda function to build/return a dispatch function to handle processing of individual grades, storing lists of these handlers for our lists of requirements or lists of actual grades, and our global functions gpaCalc and meetsAllReqs will now be processing these new lists using funcalls on the elements.

A skeletal let-over-lambda structure for grades is provided in the buildGrade function in lab2.cl, along with skeletal functions gpaCalc and meetsAllReqs, and sample calls can be seen in selftest.cl.

The command set that must be recognized by the dispatcher (the lambda returned from buildGrade) is as follows: Note that the starting selftest.cl includes sample calls using most of these commands.


Design suggestions for buildGrade:

These are simply suggestions, but might make implementing and debugging buildGrade simpler.

Design suggestions for meetsAllReqs and gpaCalc

The core logic of these two functions remains unchanged, but what has changed is that the lists are now lists of dispatchers.

As a result, where your function used to access each element of these lists as a list of the form (area num creds letter), it now instead accesses each element as a dispatcher. This means you'll need to have your functions pass the appropriate commands to the dispatcher to get the relevant data out (e.g. passing the 'isValid command, the 'getWGPA command, the 'getAsList command, etc).


Starting code/documentation

A copy of the initial lab2.cl file is provided below.

; valid strings for letter grades
(defconstant VALIDLETGRADES
   '("A+" "A" "A-" "B+" "B" "B-" "C+" "C" "C-" "D" "F" "INC" "CR" "TRF" "WDR" "UW" "CS" "NP"))

; G should be a four-element list (area coursenum credits lettergrade)
; isR should be true if G represents a requirement,
;     or false if G represents an actual grade
(defun buildGrade (G isR)
   (let
      ; local variables, currently none
      (
      )
      (labels
          ; local functions, currently none
          (
          )

      ; build and return the dispatch function,
      ;    which always expects to be passed a command and up to two optional arguments
      ;    (their purpose varies depending on which command it is)
      (lambda (cmd &optional (arg1 nil) (arg2 nil))
          (cond
             (t (format t "Error: invalid command provided ~A~%" cmd))
          ))

))) ; ends labels/let/defun



; (gpaCalc Grades)
; ----------------
; gpaCalc computes and returns the GPA for a given list of dispatchers, e.g.
;    if Grades was the list of dispatchers for
;         ("CSCI" 161 4 "A-")
;         ("ENGL" 204 3 "B")
;         ("MATH" 123 3 "B+")
;    then gpaCalc would return a gpa average of 3.33 from the three courses
;
; GPA calculation:
;    each valid grade contributes the course credit value * the gpa mark
;       to the overall gpa calculation
;    the average gpa is then the total of all the contributions
;       divided by the total credits granted
;
;    e.g.
;            "MATH" 123 3 "B+": gpa of 3.33 * 3 credits = 9.99 contrib
;            "CSCI" 161 4 "A-": gpa of 3.67 * 4 credits = 14.68 contrib
;            "ENGL" 204 3 "B":  gpa of 3.00 * 3 credits = 9.00 contrib
;       so gpa is (9.99+14.68+9.00)/10 credits = 3.367
;
;    note that
;      (1) if letterToGPA returns nil for a course then that grade
;          should not be included in the grade calculation
;      (2) if a student takes a course multiple times then they all
;          count in the gpa calculation (other than attempts eliminated by (1) above)
;
; Error handling:
;    if the Grades parameter is not a list then an error message is displayed
;       (including the value of Grades) and nil is returned,
;    otherwise the process below is followed to compute the grade
; *** as long as Grades is actually a list, you can assume all its elements
; *** represent valid dispatchers
;
;     Any invalid grades are skipped (gpaCalc itself does not end on invalid
;       individual grades and does not itself generate error messages
;       for invalid individual grades - those error messages are left to
;       the validGrades function to display)
;
;    if Grades contains no valid grades at all (e.g. it is an empty list or
;       all the entries are invalid) then a default gpa of 0 is returned
(defun gpaCalc (Grades)
   )


; (meetsAllReqs RList AList)
; --------------------------
; RList and AList are each lists of dispatchers: RList is the list of dispatchers
;    for courses/grades a student needs, and AList is the list of dispatchers
;    for courses/grades they actually have
; meetsAllReqs checks to see if every valid requirement in RList is met by at least
;    one course from AList
;
; e.g. suppose
;   RList is the list of dispatchers for
;         ("CSCI" 159 4 "C-")
;         ("CSCI" 251 3 "C-")
;         ("MATH" 123 3 "C-")
;   AList is the list of dispatchers for
;         ("ENGL" 204 3 "A-")
;         ("MATH 121 3 "A")
;         ("MATH" 123 3 "C+")
;         ("CSCI" 251 3 "TRF")
; then meetsAllReqs would return nil because of the missing 159 requirement
;
; meetsAllReqs displays a list of each requirement that has not been met,
;    showing them in the traditional 4-element list format
;
; Error handling:
;    if RList or AList are not lists then meetsAllReqs
;       displays an error message and returns nil
;    but all other error checking/messages is left to the
;       individual meetsReq commands to the various dispatchers
; *** if RList and AList are lists then you can assume each of their
; *** elements genuinely is an appropriate dispatch function
;
; Note: the courses in each list might appear in any order, do not make any assumptions
;   about courses appearing in a specific order
(defun meetsAllReqs (RList AList)
   )



selftest.cl: a test script for checking/debugging your functions

The selftest.cl script is executable. It declares the needed VALIDAREAS constant, loads the lab2.cl file, then executes any desired testing.

As with lab1, its intended use is as a testing (and debugging) mechanism for you to check that your lab2.cl functions actually work correctly.

All testing and debugging code, including test values, belongs in this script, NOT in lab2.cl. Marks will be deducted if you including testing/debugging content in your lab2.cl file.

A few sample data values and test calls are included in the initial version of selftest.cl, but these are nowhere near complete.

The initial content of selftest.cl is shown below, followed by the results of a sample run with a working lab2.cl

#! /usr/bin/sbcl --script

; ******************************************************
; THE TEST CASES/CALLS BELOW ARE MERELY A STARTING POINT
; FOR TESTING YOUR CODE, THEY ARE NOT ADEQUATE TO ENSURE
; YOUR CODE IS ANYWHERE CLOSE TO SUFFICIENT
; ******************************************************

; define a constant identifying all the valid departments/areas for the
;    purposes of the test (this is used by the validGrade function)
(defconstant VALIDAREAS '("BIOL" "CSCI" "MATH" "ENGL"))

; ensure the lab2 functions are correctly loaded
(load "lab2.cl")
(format t "---~%")
(format t "lab2.cl functions loading complete~%")
(format t "---~%")

; some basic lists for use in testing
(defvar okCS1 '("CSCI" 159 4 "C"))
(defvar okCS2 '("CSCI" 159 4 "C+"))
(defvar badGrade '("CSCI" 4 "C+"))
(defvar okMath '("MATH" 123 3 "C-"))

; try building dispatchers from the lists above
(format t "building dispatchers~%")
(defvar okReqDisp (buildGrade okCS1 t))
(defvar okGradeDisp (buildGrade okCS2 nil))
(defvar badGradeDisp (buildGrade badGrade nil))
(defvar okMathReqDisp (buildGrade okMath t))
(defvar okMathGrDisp (buildGrade okMath nil))
(format t "dispatchers built~%")
(format t "---~%")

; test various get commands on dispatchers
(format t "trying out a mix of get commands on the various dispatchers~%")
(format t "isReq on okReqDisp:~%")
   (format t "   ... ~A~%" (funcall okReqDisp 'isReq))
(format t "isReq on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'isReq))
(format t "getArea on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'getArea))
(format t "getCnum on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'getCnum))
(format t "getCred on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'getCred))
(format t "getLetG on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'getLetG))
(format t "getGPA on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'getGPA))
(format t "getWGPA on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'getWGPA))
(format t "isValid on okGradeDisp:~%")
   (format t "   ... ~A~%" (funcall okGradeDisp 'isValid))
(format t "getAsList on okGradeDisp, which came from ~A:~%" okCS2)
   (format t "   ... ~A~%" (funcall okGradeDisp 'getAsList))
(format t "isValid on badGradeDisp:~%")
   (format t "   ... ~A~%" (funcall badGradeDisp 'isValid))
(format t "---~%")

; test the meets requirements command on dispatchers
(format t "trying out the meetsReq command on dispatchers~%")
(format t "using okReqDisp, does okCS1 pass (should)~%")
    (format t "   ... ~A~%" (funcall okReqDisp 'meetsReq okCS1))
(format t "using okGradeDisp, does it meet req okCS1 (should)~%")
    (format t "   ... ~A~%" (funcall okGradeDisp 'meetsReq okCS1))
(format t "using okMathGrDisp, does it meet req okCS1 (should not)~%")
    (format t "   ... ~A~%" (funcall okMathGrDisp 'meetsReq okCS1))
(format t "---~%")

; build lists of actual/required courses for use in testing gpaCalc and meetsAll
(defvar reqsList (list okReqDisp okMathReqDisp))
(defvar actualList (list okGradeDisp okMathGrDisp))
(defvar oneCourseReq (list okMathReqDisp))
(defvar oneCourseAct (list okMathGrDisp))
(defvar actWithBad (list okGradeDisp badGradeDisp okMathGrDisp))

; test if all the requirements are met
(format t "trying out the meetsAllReqs function on lists of dispatchers~%")
(format t "(meetsAllReqs reqsList actualList) should pass~%")
(format t "   ... result: ~A~%" (meetsAllReqs reqsList actualList))

(format t "(meetsAllReqs reqsList oneCourseAct) should fail~%")
(format t "   ... result: ~A~%" (meetsAllReqs reqsList oneCourseAct))
(format t "---~%")

; simple check of gpaCalc
(format t "trying out the gpaCalcs on actualList, should give about 2.05~%")
   (format t "   ... result: ~A~%" (gpaCalc actualList))
(format t "trying out the gpaCalcs on actWithBad, should still give about 2.05~%")
   (format t "   ... result: ~A~%" (gpaCalc actWithBad))
(format t "---~%")


--- lab2.cl functions loading complete --- building dispatchers Error: (CSCI 4 C+) does not have four elements, cannot represent a grade dispatchers built --- trying out a mix of get commands on the various dispatchers isReq on okReqDisp: ... T isReq on okGradeDisp: ... NIL getArea on okGradeDisp: ... CSCI getCnum on okGradeDisp: ... 159 getCred on okGradeDisp: ... 4 getLetG on okGradeDisp: ... C+ getGPA on okGradeDisp: ... 2.33 getWGPA on okGradeDisp: ... 9.32 isValid on okGradeDisp: ... T getAsList on okGradeDisp, which came from (CSCI 159 4 C+): ... (CSCI 159 4 C+) isValid on badGradeDisp: ... NIL --- trying out the meetsReq command on dispatchers using okReqDisp, does okCS1 pass (should) ... T using okGradeDisp, does it meet req okCS1 (should) ... T using okMathGrDisp, does it meet req okCS1 (should not) ... NIL --- trying out the meetsAllReqs function on lists of dispatchers (meetsAllReqs reqsList actualList) should pass ... result: T (meetsAllReqs reqsList oneCourseAct) should fail UNMET Requirement: (CSCI 159 4 C) ... result: NIL --- trying out the gpaCalcs on actualList, should give about 2.05 ... result: 2.0471427 trying out the gpaCalcs on actWithBad, should still give about 2.05 ... result: 2.0471427 ---