Homework 11
Last updated: Wed, 29 Apr 2026 14:59:08 -0400
Out: Thu Apr 23 2026, 11am EST
Due: Thu Apr 30 2026, 11am EST
Overview
In this assignment, we will add more features, i.e., lambda and recursion, to the high-level programming language we are creating.
This hw will be graded accordingly:
correctness (Autograded) (25 pts)
design recipe (15 pts)
testing (14 pts)
style (24 pts)
README (2 pt)
Setup
Create a new repository for this assignment by going to the CS450 Spring 2026 GitHub Organization and clicking "New".
Note: The CS450 Spring 2026 GitHub Organization must be the owner of the repository. Please do not create the repository in your own account.
On the "Create a new repository" screen:
Name the repository hw<X>-<LASTNAME>-<FIRSTNAME> where <X> is the current homework number.
For example, I would name my hw11 repository hw11-Chang-Stephen.
Mark the repository as Private.
Check "Add a README file".
Select the Racket template for the .gitignore.
Choose whatever you wish for the license.
When done click "Create repository".
Updating Racket450
Make sure you have the latest version of racket450.
To do this from DrRacket, go to File -> Package Manager -> Currently Installed, search for "racket450", and then click "Update".
Alternatively, if you prefer the command line, run:
raco pkg update racket450
Reading
Review Chapters 21 and 23 of the Textbook.
NOTE: The textbook will refer to "Student Languages" which we do not use in this course (and a "Stepper" that only works with the Student Languages). Instead, we use a version of Racket tailored for this course, which is invoked by putting #lang racket450 at the top of a file (see also Before Submitting).
Also, read any relevant sections of the The Design Recipe section of the course website (topics that will be covered in future lectures are marked as such).
Tasks
The main code should go in a file named hw11.rkt that uses #lang racket450, as described previously.
NOTE (new): To make this assignment self-contained, some parts of previous solutions may be given for this assignment (see below). Nonetheless, you must still write this assignment on your own from scratch. No credit will be given if you do not do this.
As usual, all submitted code must follow the The Design Recipe. This means that language features may only be used in the correct scenarios, as called for by The Design Recipe.
UPDATE: You may use set-box! to implement recursion. For example, set! and other "imperative" features are not allowed ever.
Conditionals such as if and cond are only to be used with the appropriate Data Definitions or in other appropriate scenarios described in class.
Signatures should use define/contract and the predicates defined in the Data Design Recipe step. In this assignment, you may use the listof contract constructor where appropriate.
For Examples and Tests, do not use check-expect from the Beginning Student Language (even though the textbook says to). Instead, use check-equal? or other testing forms from rackunit (which are built into racket450, so do not explicitly require rackunit).
Examples for a function definition should be put before the define in hw11.rkt.
Tests should be put a into hw11-tests.rkt file that uses #lang racket450/testing. Try to think about corner cases and code coverage.
NOTE, on one-line helper functions: If the name and description of a "helper" function clearly describe what it does, and it clearly follows some Data Definition and all other Design Recipe steps (the course staff is the final arbiter of this), it does not need to be submitted with Examples and Tests if they are covered by other tests. ("Helper" functions are defined as functions not described in the homework assignment description.) NOTE: This does not change the Design Recipe. It is only changing submission requirements. As usual, however, we will not be able to help debug code that does not follow the Design Recipe, so omit these steps at your own risk.
All other functions should have at minimum one Example and "sufficient" Tests.
Programming
Create your programming language by implementing parse and run functions that use the following data definitions.
Program Data Definitions
For each of these data definitions, you should complete them by writing the necessary predicates, constructors, etc.
A Variable (Var) is a Symbol
An Environment (Env) is a Listof<(list Var EnvVal)> (a list of 2-element lists) where,
An EnvVal is one of:Box<Result>
Result
Represents the runtime environment for in-scope variables and the values they stand for. Entries closer to the head of the list shadow those further in the back.
The mutable Box data above is constructed using the Racket box constructor and the mutable box version of EnvVal is needed to implement recursion (via bind/rec).
Note that due to this modified Environment data definition, the env-add and env-lookup functions will need to be re-written as well (see below).
- A Program is one of:
`(arr ArraySyntax)
Variable (Var)
`(iffy ,Program ,Program ,Program)
`(bind [,Var ,Program] ,Program)
`(bind/rec [,Var ,Program] ,Program)
`(lm ,List<Var> ,Program)
(cons Program List<Program>)
Represents: the surface-level syntax of a new programming language (i.e., this is what programmers write).ArraySyntax is the same as previous hws. Since ArraySyntax is syntax and not a Program, there can be no nested Program data nested in ArraySyntax. This means that there can be no variables or other Program in ArraySyntax. For this assignment, we will properly parse the ArraySyntax by writing a parse-arraystx function (see below).
UPDATE: lm has an additional invariant that the list of Vars should be unique. Racket has check-duplicates that should help with this.
- An AST is one of:
(mk-arr Array)
(mk-var Symbol) (do not name your struct var because that is a Racket match pattern)
(mk-ify AST AST AST)
(mk-bind Symbol AST AST)
(mk-recb Symbol AST AST)
(mk-lm-ast List<Var> AST)
(mk-appl AST List<AST>)
Represents: an abstract syntax tree data structure that is produced from parsing the surface programAn Array here is the Array value like in previous assignment.
But remember, this is code that has not been run! So Array here is a subset of the possible Array Results (the latter of which can include NaN).
Also, make sure to understand the difference between an lm AST here, which represents code that has not been run, and an lm Result that can be the result of running the program.
- A Result is one of:
Array
NaN
FnResult
ErrorResult
Represents: possible results of running a program in our new language. - An FnResult is one of:
(Racket) function
(mk-lm-res List<Var> AST Environment)
Represents: functions in "CS450 Lang". The first are primitives that are in the initial environment. The second are the result of running user-definable "lambda" lm functions. Attempting to apply any Result other than an FnResult should result in an appropriate ErrorResult. - An ErrorResult is one of:
(undefined-var-err Var)
(not-fn-err Result)
(arity-err Result Listof<Result>)
(circular-err Var)
Represents: possible "error" results of running a program in our new language. The constructors are given as part of the racket450/hw11 library (see below).An "undefined" error should occur when trying to use an undefined variable. The constructor takes as input the undefined variable being referenced
A "not fn" error should occur if attempting to call a non-function. The constructor takes the non-function Result that the program attempted to apply
An "arity" error should occur when trying to apply a function to the wrong number of variables, e.g., ((lm (x y) (+ x y)) 1 2 3). The constructor takes as arguments the function Result that the program attempted to apply and also the list of arguments
A "circular" error should occur when trying to use a recursive reference that has not been completely defined yet, e.g., (bind/rec [f f] f)
Some subtleties about error propagation:If one of these errors occurs in an inner expression, it should propagate to be the final result of the whole expression, but "undefined" error should have precedence. For example, applying an undefined variable should produce "undef" error and not "not fn" error
If more than one error occurs at the same "level", e.g., in arguments of a function call, the error result should be determined using a left-to-right precedence.
Array and Data Definitions
- An Atom is one of:
Number
Bool
NaN (UPDATE: forgot this in the initial hw description)
- An Array is one of:
Atom
ArrayList
An ArrayList is one of:Further, an Array has an additional invariant that it is "rectangular", meaning that along any one dimension, each element must have the same length. For example, in a 2d Array, all the rows must have the same length.
Functions (define and provide)
parse : takes a Program and produces an AST abstract syntax tree data value. When the input is not a valid Program, this function should call raise-syntax-error with an exn:fail:syntax:cs450 exception, which is given in racket450/hw11, and an appropriate error message. Be careful here because what is a valid/invalid Program has changed and may require more sophisticated parsing to determine.
parse-arraystx : Given ArraySyntax input, returns a valid Array containing only literal values, e.g., numbers and booleans. Raises exn:fail:syntax:cs450 if given invalid ArraySyntax. Since ArraySyntax does not contain Program data, it should not be possible to use variable references or other Program in ArraySyntax. For example, (parse-arraystx '[1 2 x]) should raise a syntax exception.
run : takes an AST tree and "runs" it, to produce a Result "result"
As mentioned in lecture, operations on arrays, should follow the behavior of NumPy. When dealing with Atoms, operations should follow JavaScript semantics. We will use the repljs.com evaluator as the official specification for this behavior.
Note (new): This run function should normally define an internal "run" function that uses an Environment accumulator (that follows all steps of the Accumulator Design Recipe), but since internal functions cannot be tested as easily, this assignment requires that this accumulator function be defined external to the main run function.
You will also need to define apply-result (described below), which should also normally be inside the run function define, but for this assignment define it at the top-level to make it easier to test.
Initial environment: should contain bindings for the - and *, which should correspond to the appropriate array function constructed with hw11-mk-array-op like in previous hws.
The semantics of the elementwise operations should follow JavaScript semantics. When in doubt, we will use the repljs.com evaluator as the official specification for the behavior of arithmetic on all kinds of values in our new programming language.
bind and bind/rec add Vars to the environment with the difference that in bind/rec, the Program that will produce the Result that the Var represents can refer to the Var being defined (i.e., a self-reference), while this is not true for non-recursive bind. For example, (bind [f f] ....) should evaluate to undefined-var-err while (bind/rec [f f] ....) should not. For reference, the behavior of bind and bind/rec should be similar to the behavior of let and letrec in Racket, respectively.
Your run function in this assignment should be able to define and apply recursive functions like the fac example from lecture:
(check-equal? (run (parse '(bind/rec [fac (lm (n) (iffy n (* n (fac (- n 1))) 1))] (fac 5)))) 120)(check-equal? (run (parse '(bind/rec [fac (lm (n) (iffy n (* n (fac (- n (arr 1)))) (arr 1)))] (fac (arr 5))))) 120) env-add : Takes an Environment, Var, and EnvVal and returns a new Environment where the given Var is bound to the given EnvVal
env-lookup : Takes an Environment and Var and returns either the EnvVal Result that the given Var represents, or an undefined-var-err, if the given Var is not in the environment. The the Var is bound to a Box<Result>, this function should get the Result out of the box before returning it.
apply-result : Takes a Result that is a function and a Listof<Result> representing arguments and applies function to the arguments. Produces either the result of application, if given a valid FnResult and the correct number of arguments, or an appropriate ErrorResult if the application failed:
not-fn-err if the first argument is not a function
arity-err if the given arguments does not match the arity of the given function
a propagation of error if any of the given inputs is already an ErrorResult
Note that while this function should normally be internal to run, instead it should be defined at the top-level so it may be independently tested
Library Functions
In order to be self-contained, a library racket450/hw11 will be available that contains some parts of solutions to previous assignments. (Note: This library will not be available until all students have submitted the previous hws.) Import this library by writing (require racket450/hw11). You may of course use your own implementations of the functions below as well.
The library will contain the following functions:
hw11-Atom? evaluates to true for Atom data values in this hw
hw11-Array? evaluates to true for Array data values in this hw
hw11-mk-array-op Takes as input an implementation of an elementwise function with signature (-> hw11-Atom? hw11-Atom? hw11-Atom?) and returns an array version of that function with signature (-> hw11-Array? hw11-Array? hw11-Array?). E.g., passing in array-element* and returns an implementation of array*, etc. The output function raises exn:fail:cs450:broadcast if the given inputs are not compatible.
exn:fail:cs450:broadcast? Evaluates to true if given a broadcast exception produced by the hw11-mk-array-op function above. May be useful for testing.
exn:fail:syntax:cs450 Exception for syntax errors produced by your parse function.
exn:fail:syntax:cs450? predicate for cs450 syntax error exceptions.
undefined-var-err Takes a symbol and produces a value representing an "undef var" ErrorResult
undefined-var-err? Predicate for above ErrorResult value
not-fn-err Takes a (non-function) Result and produces a value representing a "applied non-fn" ErrorResult
not-fn-err? Predicate for above ErrorResult value
arity-err Takes a function Result and the list of (wrong number of) arguments that the function and returns a value representing "arity" ErrorResult
arity-err? Predicate for above ErrorResult value
circular-err Takes a symbol and produces a value representing a "circular" recursive reference ErrorResult
circular-err? Predicate for above ErrorResult value
hw11-ErrorResult? evaluates to true for the four ErrorResult values above
NaN "not a number" Result
NaN? predicate the returns true when given NaN
Before Submitting
Testing (and Autograders)
Before submitting, note:
Each programmer is solely responsible for testing their program to make sure it’s correct. Do not submit until all code has been has a "sufficient" number of Test cases that verify its correctness.
Note that there is no GradeScope "Autograder" available for students to use (an Autograder is not a software development/testing tool anyways, so it should not be used as one).
Thus, no questions mentioning an Autograder will be answered, e.g., posts asking "why is the Autograder giving an error?" are not allowed.
If you happen to find an Autograder and decide to look at its output despite this warning, please understand that it may be incorrect or incomplete, change at any time, or have random behavior, and that it in no way indicates the grade of the submitted hw.
Anyone that does get useful information from an Autograder, e.g., a failing test case or crashing code report, should treat it as bonus information (that you otherwise would not have had) that you and you alone must determine what to do with.
Regardless of what any Autograder might say, all code must still be independently tested to be correct before it is submitted.
The proper way to ask questions is with small code examples. This means that each question must include a small example code snippet along with what the "expected" result should be!
Further, any posted examples should contain the minimal amount of code needed to explain the question. Full file dumps or anything more than a few lines will not be accepted. More is not better. In fact it’s worse because it takes longer to read and is less likely to get a good answer.
Style
All code should follow proper Racket Style.
Also, the repository itself must follow proper style. Specifically, it must have appropriate commit messages. See How to Write a Git Commit Message if you are unsure how to write a commit message.
Note: Do not use the "file upload" feature on Github. The course staff may not accept hw uploaded in this way.
Files
A submission must have the following files in the repository root:
hw11.rkt: Contains the hw solution code.
The first line should be #lang racket450.
All defines should use the name specified in the exercise (ask if you are unsure).
hw11-tests.rkt: This file should use the #lang racket450/testing language.
It should also require hw11.rkt and define tests for it.
Specifically, it should contain "sufficient" Test cases (e.g., check-equal?, etc.) for each defined function.
README.md: Contains the required README information, including the GitHub repo url.
Submitting
When you are done, submit your work to Gradescope hw11. You must use the "GitHub" Submission Method and select your hw<X>-<LASTNAME>-<FIRSTNAME> repository.
Note that this is the only acceptable way to submit homework in this course. (Do not manually upload files and do not email files to the course staff. Homework submitted via any unapproved methods will not be graded.)

