|
1 | 1 | #lang scribble/manual |
2 | | -@(require "../defns.rkt") |
3 | | -@title[#:tag "Assignment 7" #:style 'unnumbered]{Assignment 7: Binding sequentially, n-ary prmimitives} |
| 2 | +@(require "../defns.rkt" |
| 3 | + "../notes/ev.rkt") |
| 4 | + |
| 5 | +@title[#:tag "Assignment 7" #:style 'unnumbered]{Assignment 7: |
| 6 | +Variable-arity and arity-overloaded functions} |
4 | 7 |
|
5 | 8 | @(require (for-label a86 (except-in racket ...))) |
6 | 9 |
|
7 | 10 | @bold{Due: @assign-deadline[7]} |
8 | 11 |
|
9 | | -Details of this assignment will be released later in the semester. |
| 12 | +@(ev '(require iniquity-plus)) |
| 13 | + |
| 14 | +The goal of this assignment is to implement variable arity functions. |
| 15 | + |
| 16 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Overview} |
| 17 | + |
| 18 | +For this assignment, you are given a @tt{iniquity-plus.zip} file on ELMS |
| 19 | +with a starter compiler similar to the @seclink["Iniquity"]{Iniquity} |
| 20 | +language we studied in class. |
| 21 | + |
| 22 | +@section[#:tag-prefix "a7-" #:style 'unnumbered #:tag "rest"]{Rest |
| 23 | +arguments} |
| 24 | + |
| 25 | +Many languages including JavaScript, C, C++, Java, Ruby, Go, PHP, and |
| 26 | +Racket provide facilities for defining functions that take a ``rest |
| 27 | +argument'' which allows the function to be called with more arguments |
| 28 | +than expected and these additional arguments will be bound to a single |
| 29 | +value that collects all of these arguments. In Iniquity, as in |
| 30 | +Racket, the obvious way of collecting these arguments into a single |
| 31 | +value is to use a list. |
| 32 | + |
| 33 | +Here are some examples: |
| 34 | + |
| 35 | +@itemlist[ |
| 36 | + |
| 37 | +@item{@racket[(define (f . xs) ...)]: this function takes @emph{any} number |
| 38 | +of arguments and binds @racket[xs] to a list containing all of them,} |
| 39 | + |
| 40 | +@item{@racket[(define (f x . xs) ...)]: this function takes @emph{at |
| 41 | +least} one argument and binds @racket[x] to the first argument and |
| 42 | +@racket[xs] to a list containing the rest. It's an error to call this function |
| 43 | +with zero arguments.} |
| 44 | + |
| 45 | +@item{@racket[(define (f x y z . xs) ...)]: this function takes |
| 46 | +@emph{at least} three arguments and binds @racket[x], @racket[y], and |
| 47 | +@racket[z] to the first three arguments and @racket[xs] to a list |
| 48 | +containing the rest. It's an error to call this function with 0, 1, |
| 49 | +or 2 arguments.} |
| 50 | +] |
| 51 | + |
| 52 | +Here are some examples in Racket to get a sense of the behavior: |
| 53 | + |
| 54 | +@ex[ |
| 55 | +(define (f . xs) (list xs)) |
| 56 | +(f) |
| 57 | +(f 1) |
| 58 | +(f 1 2) |
| 59 | +(f 1 2 3) |
| 60 | +(f 1 2 3 4) |
| 61 | +(define (f x . xs) (list x xs)) |
| 62 | +(eval:error (f)) |
| 63 | +(f 1) |
| 64 | +(f 1 2) |
| 65 | +(f 1 2 3) |
| 66 | +(f 1 2 3 4) |
| 67 | +(define (f x y z . xs) (list x y z xs)) |
| 68 | +(eval:error (f)) |
| 69 | +(eval:error (f 1)) |
| 70 | +(eval:error (f 1 2)) |
| 71 | +(f 1 2 3) |
| 72 | +(f 1 2 4) |
| 73 | +] |
| 74 | + |
| 75 | +The code generated for a function call should not change: it should |
| 76 | +pass all of the arguments on the stack along with information about |
| 77 | +the number of arguments. |
| 78 | + |
| 79 | +The compilation of function definitions that use a rest argument |
| 80 | +should generate code that checks that the given number of arguments is |
| 81 | +acceptable and should generate code to pop all ``extra'' arguments off |
| 82 | +the stack and construct a list which is then bound to the rest |
| 83 | +parameter. |
| 84 | + |
| 85 | +It is worth remembering that arguments are pushed on the stack in such |
| 86 | +a way that the last argument is the element most recently pushed on |
| 87 | +the stack. This has the benefit of making it easy to pop off the |
| 88 | +extra arguments and to construct a list with the elements in the |
| 89 | +proper order. |
| 90 | + |
| 91 | +HINT: the function definition knows the number of ``required'' |
| 92 | +arguments, i.e. the minimum number of arguments the function can be |
| 93 | +called with---call this @math{m}---and the caller communicates how |
| 94 | +many actual arguments have been supplied---call this @math{n}. The |
| 95 | +compiler needs to generate a loop that pops @math{n-m} times, |
| 96 | +constructing a list with with popped elements, and then finally pushes |
| 97 | +this list in order to bind it to the rest parameter. |
| 98 | + |
| 99 | + |
| 100 | +@section[#:tag-prefix "a7-" #:style 'unnumbered #:tag "case-lambda"]{Arity dispatch} |
| 101 | + |
| 102 | +Some languages such as Java, Haskell, and Racket make it possible to |
| 103 | +overload a single function name with multiple definitions where the |
| 104 | +dispatch between these different definitions is performed based on the |
| 105 | +number (or kind) of arguments given at a function call. |
| 106 | + |
| 107 | +In Racket, this is accomplished with the @racket[case-lambda] form for |
| 108 | +constructing multiple-arity functions. |
| 109 | + |
| 110 | +Here is an example: |
| 111 | + |
| 112 | +@ex[ |
| 113 | +(define f |
| 114 | + (case-lambda |
| 115 | + [(x) "got one!"] |
| 116 | + [(p q) "got two!"])) |
| 117 | + |
| 118 | +(f #t) |
| 119 | +(f #t #f) |
| 120 | +(eval:error (f #t #f 0)) |
| 121 | +] |
| 122 | + |
| 123 | +This function can accept @emph{either} one or two arguments. If given |
| 124 | +one argument, it evaluates the right-hand-side of the first clause |
| 125 | +with @racket[x] bound to that argument. If given two arguments, it |
| 126 | +evaluates the right-hand-side of the second clause with @racket[p] and |
| 127 | +@racket[q] bound to the arguments. If given any other number of |
| 128 | +arguments, it signals an error. |
| 129 | + |
| 130 | +A @racket[case-lambda] form can have any number of clauses (including |
| 131 | +zero!) and the first clause for which the number of arguments is |
| 132 | +acceptable is taken when the function is called. |
| 133 | + |
| 134 | +Note that @racket[case-lambda] can be combined with rest arguments too. |
| 135 | +A clause that accepts any number of arguments is written by simply |
| 136 | +listing a parameter name (no parentheses). A clause that accepts some |
| 137 | +non-zero minimum number of parameters is written with a dotted |
| 138 | +parameter list. |
| 139 | + |
| 140 | +For example: |
| 141 | + |
| 142 | +@ex[ |
| 143 | +(define f |
| 144 | + (case-lambda |
| 145 | + [(x y z . r) (length r)] |
| 146 | + [(x) "just one!"])) |
| 147 | + |
| 148 | +(f 1 2 3 4 5 6) |
| 149 | +(f #t) |
| 150 | +(eval:error (f)) |
| 151 | +(eval:error (f 1 2))] |
| 152 | + |
| 153 | +This function takes three or more arguments @emph{or} one argument. Any |
| 154 | +other number of arguments (i.e. zero or two) results in an error. |
| 155 | + |
| 156 | +@ex[ |
| 157 | +(define f |
| 158 | + (case-lambda |
| 159 | + [(x y z) "three!"] |
| 160 | + [xs (length xs)])) |
| 161 | + |
| 162 | +(f) |
| 163 | +(f 1 2) |
| 164 | +(f 1 2 3) |
| 165 | +(f 1 2 3 4 5 6) |
| 166 | +] |
| 167 | + |
| 168 | +This function takes any number of arguments, but when given three, it |
| 169 | +produces @racket["three!"]; in all other cases it produces the number |
| 170 | +of arguments. |
| 171 | + |
| 172 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Representing the |
| 173 | +syntax of function definitions} |
| 174 | + |
| 175 | +The @seclink["Iniquity"]{Iniquity} language has a single function |
| 176 | +definition form: @racket[(define (_f _x ...) _e)] which is represented |
| 177 | +with the following AST type: |
| 178 | + |
| 179 | +@#reader scribble/comment-reader |
| 180 | +(racketblock |
| 181 | +;; type Defn = (Defn Id (Listof Id) Expr) |
| 182 | +(struct Defn (f xs e) #:prefab) |
| 183 | +) |
| 184 | + |
| 185 | +Because there are three different forms of function definition in |
| 186 | +Iniquity+, we use the following AST representation: |
| 187 | + |
| 188 | +@#reader scribble/comment-reader |
| 189 | +(racketblock |
| 190 | +;; type Defn = (Defn Id Fun) |
| 191 | +(struct Defn (f fun) #:prefab) |
| 192 | + |
| 193 | +;; type Fun = (FunPlain [Listof Id] Expr) |
| 194 | +;; | (FunRest [Listof Id] Id Expr) |
| 195 | +;; | (FunCase [Listof FunCaseClause]) |
| 196 | +;; type FunCaseClause = (FunPlain [Listof Id] Expr) |
| 197 | +;; | (FunRest [Listof Id] Id Expr) |
| 198 | +(struct FunPlain (xs e) #:prefab) |
| 199 | +(struct FunRest (xs x e) #:prefab) |
| 200 | +(struct FunCase (cs) #:prefab) |
| 201 | +) |
| 202 | + |
| 203 | +What used to be represented as @racket[(Defn _f _xs _e)] is now |
| 204 | +represented as @racket[(Defn _f (FunPlain _xs _e))]. |
| 205 | + |
| 206 | + |
| 207 | +The parser already works for these new forms of function definitions. |
| 208 | +Here are some examples of how function definitions are parsed, but you |
| 209 | +are encouraged to try out more to get a better sense: |
| 210 | + |
| 211 | +@ex[ |
| 212 | +(parse-define '(define (f x) x)) |
| 213 | +(parse-define '(define (f . xs) xs)) |
| 214 | +(parse-define '(define (f x y z . q) q)) |
| 215 | +(parse-define |
| 216 | + '(define f |
| 217 | + (case-lambda |
| 218 | + [(x y) 2] |
| 219 | + [(z) 1] |
| 220 | + [(a b c . d) "3+"] |
| 221 | + [q "other"]))) |
| 222 | +] |
| 223 | + |
| 224 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Starter code} |
| 225 | + |
| 226 | +The compiler code given to you is just an implementation of Iniquity, |
| 227 | +but updated to parse the new forms of function definitions and |
| 228 | +re-organized slightly to match the new AST representation. |
| 229 | + |
| 230 | +The interpreter code given to you works on the full Iniquity+ |
| 231 | +language, so you do not need to update @racket[interp.rkt] and can use |
| 232 | +the interpreter to guide your implementation of the compiler. |
| 233 | + |
| 234 | +@ex[ |
| 235 | +(interp |
| 236 | + (parse '(define (f x) x) |
| 237 | + '(f 1))) |
| 238 | +(interp |
| 239 | + (parse '(define (f . x) x) |
| 240 | + '(f 1))) |
| 241 | +(interp |
| 242 | + (parse '(define (f . x) x) |
| 243 | + '(f))) |
| 244 | +(interp |
| 245 | + (parse '(define (f . x) x) |
| 246 | + '(f 1 2 3 4 5))) |
| 247 | +(interp |
| 248 | + (parse '(define f |
| 249 | + (case-lambda |
| 250 | + [(x y) 2] |
| 251 | + [(z) 1] |
| 252 | + [(a b c . d) "3+"] |
| 253 | + [q "other"])) |
| 254 | + '(cons (f 7) |
| 255 | + (cons (f 3 4) |
| 256 | + (cons (f) |
| 257 | + (cons (f 7 8 9 10 11) |
| 258 | + '())))))) |
| 259 | +] |
| 260 | + |
| 261 | + |
| 262 | +Thus, you should only need to modify @racket[compile.rkt]. |
| 263 | + |
| 264 | +A small number of test cases are given as usual. |
| 265 | + |
| 266 | +@section[#:tag-prefix "a7-" #:style 'unnumbered]{Submitting} |
| 267 | + |
| 268 | +To submit, use @tt{make} from within the @tt{iniquity-plus} directory |
| 269 | +to create a zip file containing your work and submit it to Gradescope. |
0 commit comments