1.
(a) Pode-se considerar os tipos Env
e Mem
como abstratos, ou usar Map
, tanto faz:
def eval(funs: Env[Fun1])(env: Env[Int]): (Int, Mem) => (Int, Int, Mem)
(b) Claro que vários trechos podem ser apresentados, contanto que combinem let
com
atribuição na expressão do let
:
let a = 1 in
let b = (a = 2) in
print(a)
end
end
O programa acima imprime 2
em um interpretador correto, e 1
em um interpretador
errado.
(c) O uso de sp1
três vezes no código abaixo não é uma falha de linearidade, pois só
estamos realmente usando sp1
quando passamos seu valor para o resultado de um eval
:
case Let(nome, exp, corpo) => (sp, mem) => {
val (ve, sp1, mem1) = exp.eval(funs)(env)(sp, mem)
val (vc, sp2, mem2) = corpo.eval(funs)(env + (nome -> sp1))(sp1 + 1, mem1 + (sp1 -> ve))
(vc, sp2 - 1, mem2 - (sp2 - 1))
}
2. A memória na resposta pode ficar implícita ou explícita. Com memória implícita:
case Soma(e1, e2) => k =>
e1.eval(funs)(env)(v1 =>
e2.eval(funs)(env)(v2 =>
(v1, v2) match {
case (NumV(n1), NumV(n2)) => k(NumV(n1 + n2))
case _ => sys.error("soma precisa de dois números")
}))
E com memória explícita:
case Soma(e1, e2) => k => mem =>
e1.eval(funs)(env)(v1 => mem1 =>
e2.eval(funs)(env)(v2 => mem2 =>
(v1, v2) match {
case (NumV(n1), NumV(n2)) => k(NumV(n1 + n2))(mem2)
case _ => sys.error("soma precisa de dois números")
})
(mem1))
(mem)
3. Quase todas as funções apenas passam seu resultado para a continuação,
só com func
temos que trabalhar mais:
fun dois(k)
k(2)
end
fun dobra(x, k)
k(x * 2)
end
fun soma(a, b)
k(a + b)
end
fun func(k)
dois(fun (a)
dobra(a, fun (b)
soma(a, b, k)
end))
end
Note como a continuação recebida por func
é apenas passada pra soma
,
pois é ela que tem que receber o resultado de func
.