![]() |
![]() |
|
Due
We continue to build on the language implementation of OG1/OG2. You can implement this homework in either Java or Racket. You may use the OG2-soln if you want. Link is usuable once you E2L-submit OG2; just let me know if you need to bypass that.
OG3 is just like OG2, except we now allow one variable to shadow another.
For example, we might have a iykyk x irl … wya …
which in turn contains another iykyk x irl … wya …
somewhere inside of it.
In that case the inner x should shadow the outer one:
⇒
iykyk x irl 7 wya iykyk x irl 5 wya finna rizz x 3
⇒
iykyk x irl 5 wya finna rizz x 31
⇒
finna rizz 5 3
⇒
8.
And of course,
shadowing may occur between non-adjacent scopes:
iykyk x irl 3 wya iykyk y irl 4 wya iykyk x irl 5 wya ….
So what does our interpreter need to do? Well, when eval does substitution in a LetExpr, we just need to be a bit cautious: If we're substituting every x in an Expr, and we encounter a iykyk x irl Exprrhs wya Exprbody then we shouldn't substitute any xs inside the Exprbody. Though of course, if we encounter a iykyk y irl Exprrhs wya Exprbody, then this doesn't affect our substitution.
Using our programming-language vocabulary: when substituting, only substitute “free occurrences” of an Id in E1, not any “bound occurrences”2.
As an example, for the expression
iykyk x irl 3 wya bruh iykyk y irl 4 wya finna rizz x bruh iykyk x irl 5 wya finna rizz x y slaps slaps,
we have the syntax tree drawn at
the right albeit with
rizz
written as boii
,
with a dotted-arrow from each binding-occurrence to its bound occurrence(s).
This corresponds to some runnable test-cases3:
(check-expect (eval (string->expr "iykyk x irl 3 wya bruh iykyk y irl 4 wya finna rizz x bruh iykyk x irl 5 wya finna rizz x y slaps slaps")) (+ 3 (+ 5 4))) (check-expect (subst "x" 3 (string->expr "bruh iykyk y irl 4 wya finna rizz x bruh iykyk x irl 5 wya finna rizz x y slaps slaps")) (string->expr "bruh iykyk y irl 4 wya finna rizz 3 bruh iykyk x irl 5 wya finna rizz x y slaps slaps")) |
hint/spoiler: in iykyk zed irl Exprinitialize wya Exprbody, we know thatzed can never occur free in Exprbody. We don't even need to look inside it, if we are only looking for free occurrences of (a surrounding) zed!
Update OG2 to OG3,
by the necessary changes to enable shadowing.
You are encouraged to build on your own previous solution,
but you may also use the
OG2-soln (.rkt)
OG4 adds (non-recursive) functions and function-application to our language:
Expr → … | FuncExpr | <FuncApplyExpr> ;>>>OG4 FuncExpr → iso Id wya Expr ;>>>OG4 Interpretation: function-value; the Id is the parameter, and the Expr is the function's body. FuncApplyExpr → ohio Expr swoop Expr ;>>>OG4 Interpretation: apply the function (first Expr) to the argument (2nd Expr). |
Here is
the function (λ (x) (+ (* 3 x) 1)) written in OG4:
iso x wya finna rizz finna glow up x 3 1.
And, here is the (uniterated) collatz function,
(λ(n) (if (even? n) (* n 1/2) (+ (* 3 n) 1))), written in OG4:
iso n wya no cap? finna cancel n 2 ong finna glow up n 0.5 bet finna rizz finna glow up 3 n 1 |
A FuncApplyExpr represents calling a function. Here are two expressions, both evaluating to 5·3+1 = 16:
iykyk tripleAndInc irl iso x wya finna rizz finna glow up x 3 1 body of `tripleAndInc` wya ohio tripleAndInc swoop 5 call tripleAndInc(5)… ohio iso x wya finna rizz finna glow up x 3 1 swoop 5 Equivalent to above: apply a function-literal (w/o bothering to give it a name) |
isoand
finna. They'll just be in comments since they're not racket code. Or hey, you can put them in a string, and then pass those strings to parse as test-cases!.
Note: You won’t be able to evaluate function-applications for recursive functions yet (see OG5), but we can still write the test cases! (You can comment out that one test case for now, since it’ll trigger a run-time exception otherwise.)
(define (make-adder n) (lambda (m) (+ n m))) ((make-adder 3) 4) ; evals to 7 ; Note that `(make-adder 3)` evals to `(lambda (m) (+ 3 m))` ; the `((` means we have *two* function-applications: ; we first call `make-adder` (getting back a lambda-value), ; then we call that result we got back. |
Semantics of a function-literal: Just as numbers are self-evaluating, so are FuncExprs. Evaluating (an internal representation of) a function results in that same (internal representation of the) function. We won’t actually evaluate the body until the function is applied. (This is exactly how Java, racket, python, javascript, etc. treat functions.)
Testing function-values: How can we make a test-cases, when the result isn't a number? For example, for evaluating iso n wya finna rizz n 1 or ohio makeAdder swoop 5 (whose expected-results are both <FuncExpr> structs). You have two approaches:
(check-expect (eval (string->expr "iso n wya finna rizz n 1")) (make-my-func-expr "n" (make-binop "rizz" "n" 1))) |
["iso n wya finna rizz n 1" ,(make-my-func-expr "n" (make-binop "rizz" "n" 1))] |
[optional] Why the comma?: If you want to understand why it doesn't work without a comma, and what the comma does for us, you can read backquote.html (recommended, but not necessary to complete this homework).tl;dr: The test-harness's list of program+result pairs isn't created by calling list (over and over for each pair); instead it was created by using a single backquote, “`”: (define all-tests `(("finna rizz 2 3" 5) …). This quote then distributes over all the nested open-parens to create sub-lists woo-hoo!, a handy time-saver. In fact, it also quotes the values inside those sub-lists — which didn't previously matter to us, because quoting string-literals and integer-literals has no effect ('16 is 16, and '"hello" is "hello").
However: quoting an identifier turns it into a symbol ('hello is 'hello :-). So `(3 (sqrt 16) 5) is (list 3 (list 'sqrt 16) 5), and not (list 3 4 5) like we hoped. To suppress the quoting of (sqrt 16), we precede it with a comma: `(3 ,(sqrt 16) 5) gives (list 2 3 4). (The comma is known as unquote.)
The semantics of eval’ing the function-application ohio Expr0 swoop Expr1:
external resource: Another description of this algorithm is in VT's textbook's sectionA Substitution-Based Model of Evaluation.
You can submit your prolog queries inside a comment of a (racket) file, at the top of your submitted hardcopy, thanks! Only include your added rules/facts, not the rest of the provided .pl file.
Note: Put our prolog queries inside a comment of a (racket) file, at the top of your submitted hardcopy, thanks! Only include your added rules/facts, not the rest of the provided .pl file.
Prolog paths (transitive closure):
Continuing from defining compatible in the previous problem,
write friendly:
We say two super-people are friendly if
they are connected through some chain of compatible people.
For example,
bubbles
and
rafael
would be friendly if
compatible(bubbles,magikarp), compatible(magikarp,squirtle),
and
compatible(squirtle,rafael).
(That is: friendly is the reflexive, transitive closure of the compatible relation.)
Everybody, of course, is trivially friendly with themselves
(since they’re connected by a chain of 0 compatible others).
Note:It can be okay if you get infinite loops:
Since there can be cycles of friendly people (unlike our ancestor example), prolog (which uses depth-first search, rather than breadth-first), can get into infinite loops! In particular, asking friendly(bubbles,some_person_who_isnt_friendly_w_bubbles) can trigger an infinite loop. Asking friendly(bubbles,X) will give you back the same solutions repeatedly. (But asking about two people who are friendly should work just fine.) We'll just ignore such loops, for this homework.(Fwiw: the general solution would be to either (a) add an “exclusion” list of people already tried5, or (b) use datalog, a restricted version of prolog which uses BFS, and is always guaranateed to terminate (See #lang racklog, in racket), or (c) add
We're using prolog to learn about the mindset of declarative programming (and its cool pattern-matching/unification) — I'm not interested in us learning about the details of prolog's internal algorithms.
The interpreter project is based on the first chapters of Programming Languages and Interpretation, by Shriram Krishnamurthi. As a result, this homework assignment is covered by the Creative Commons Attribution-NonCommercial-ShareAlike 3.0 United States License. Although we’re using a different dialect of racket than that book, you might find it helpful to skim it.
eval(string->expr("iykyk x irl 5 wya finna rizz x 3")) = eval(string->expr("finna rizz 5 3")) = eval(string->expr("8")) |
This page licensed CC-BY 4.0 Ian Barland Page last generated | Please mail any suggestions (incl. typos, broken links) to ibarland ![]() |