For this assignment, you will modify Paret to use call-by-need (i.e. lazy) evaluation, and also to have pairs.
You will add pairs to the language. (pair fst snd)
constructs a pair with two elements, fst
and
snd
. (first p)
gets the first element
of a pair p
, and (second p)
gets the
second element. If the argument of first
or second
is not a pair, you should raise err-not-a-pair
with the value. (is-pair v)
returns true if v
is a pair value, and false if v
is other kinds of value.
Lazy evaluation means that arguments to functions should not be evaluated if they are not
used in the body. As a result, many places your interpreter that used to work
with values, will instead work with Computation
s:
data Computation:
| c-suspend(body :: Expr, env :: Env)
| c-value(value :: Value)
end
A Computation
is either a c-suspend
,
which represents an argument that has
not yet been evaluated, or a c-value
, which has.
Function calls and the pair
constructor must be
lazy: that is, a function
must not evaluate its argument if it is not used, and a pair must not
evaluate its parts if they are not used. let
's value should
also be lazy, and you should make sure that desugaring will respect this.
On the other hand, other primitives in the language should force their arguments to be
evaluated. Specifically, operators should force their arguments to be evaluated, if
should force its condition
to be evaluated, and first
and second
should force the accessed element of the pair to be
evaluated. Likewise, the top-level
interpreter should force its result to be evaluated (This is why eval
, in
the code stencil, returns a Value
rather than a
Computation
). All evaluations should be shallow. That is, it should not recursively force values inside.
For efficiency, when a computation is forced, its result should be cached so
that it doesn't have to be computed a second time. We have done this for you
in the code stencil with the force
function. It uses structural mutation to
make sure that a computation is only performed once (ref
marks a field as
mutable in Pyret):
data RefComputation:
| r-comp(ref comp :: Computation)
end
<expr> ::= <num>
| true
| false
| <string>
| (+ <expr> <expr>)
| (++ <expr> <expr>)
| (num= <expr> <expr>)
| (str= <expr> <expr>)
| (if <expr> <expr> <expr>)
| (and <expr> <expr>)
| (or <expr> <expr>)
| (let (<id> <expr>) <expr>)
| <id>
| (lam <id> <expr>)
| (<expr> <expr>)
| (pair <expr> <expr>)
| (first <expr>)
| (second <expr>)
| (is-pair <expr>)
type Env = StringDict<RefComputation>
data Value:
| v-num(value :: Number)
| v-str(value :: String)
| v-bool(value :: Boolean)
| v-fun(param :: String, body :: Expr, env :: Env)
| v-pair(first :: RefComputation, second :: RefComputation)
end
data Computation:
| c-suspend(body :: Expr, env :: Env)
| c-value(value :: Value)
end
data RefComputation:
| r-comp(ref comp :: Computation)
end
data Expr:
| e-num(value :: Number)
| e-str(value :: String)
| e-bool(value :: Boolean)
| e-op(op :: Operator, left :: Expr, right :: Expr)
| e-if(cond :: Expr, consq :: Expr, altern :: Expr)
| e-lam(param :: String, body :: Expr)
| e-app(func :: Expr, arg :: Expr)
| e-id(name :: String)
| e-pair(fst :: Expr, snd :: Expr)
| e-first(pair :: Expr)
| e-second(pair :: Expr)
| e-is-pair(expr :: Expr)
| sugar-and(left :: Expr, right :: Expr)
| sugar-or(left :: Expr, right :: Expr)
| sugar-let(name :: String, expr :: Expr, body :: Expr)
end
data Operator:
| op-plus
| op-append
| op-str-eq
| op-num-eq
end
data InterpError:
| err-if-got-non-boolean(val :: Value)
| err-bad-arg-to-op(op :: Operator, val :: Value)
| err-unbound-id(name :: String)
| err-not-a-function(val :: Value)
| err-not-a-pair(val :: Value)
end
(For reference, feel free to look at the definitions file.)
To get started, you can open the code stencil and the
test stencil in code.pyret.org
.
Please read the test guidelines
Like closure, programs can evaluate to a pair value. Since your implementation affects the representation of pair values, in your test submission you should only test whether the program returns a pair, not which specific pair it returned. Likewise, if you're testing for an exception, make sure not to test that it contains a specific pair value.
On the other hand, in your implementation file, you are free to test whether
the program returns a specific pair or a specific function. Note however that these values contain
RefComputation
s which are mutable, so you have to use is=~
instead of is
. See more details here.