在另一个问题中,Bob提出了以下interpreter for the untyped lambda calculus。
data Expr = Var String | Lam String Expr | App Expr Expr
data Value a = V a | F (Value a -> Value a)
interpret :: [(String, Value a)] -> Expr -> Value a
interpret env (Var x) = case lookup x env of
Nothing -> error "undefined variable"
Just v -> v
interpret env (Lam x e) = F (\v -> interpret ((x, v):env) e)
interpret env (App e1 e2) = case interpret env e1 of
V _ -> error "not a function"
F f -> f (interpret env e2)
Ivan Zakharyaschev remarked此解释器由于F f -> f (interpret env e2)
而按值调用。 名称解析员的实施方式与上述方法有何不同?
Plotkin在20世纪70年代研究call-by-name and call-by-value strategies评估lambda演算。
答案 0 :(得分:5)
我不认为使用原始数据定义可以正确调用名称。 F (Value a -> Value a)
有Value a
作为参数,所以我们别无选择,只能传递一些已解释的值,并且它将在Haskell减少行为下成为需要调用。
我们可以修改数据定义:
data Value a = V a | F ((() -> Value a) -> Value a)
还有解释器返回明确的thunk:
interpret :: [(String, () -> Value a)] -> Expr -> () -> Value a
interpret env (Var x) = delay (case lookup x env of
Nothing -> error "undefined variable"
Just v -> force v)
interpret env (Lam x e) = delay (F (\v -> force (interpret ((x, v):env) e)))
interpret env (App e1 e2) = delay (case force (interpret env e1) of
V _ -> error "not a function"
F f -> f (interpret env e2))
force :: (() -> a) -> a
force f = f ()
{-# NOINLINE force #-}
delay :: a -> () -> a
delay a () = a
{-# NOINLINE delay #-}
现在,我们不是在环境中存储thunk,而是存储partial application object,然后在不同的呼叫站点单独评估它。
force
和delay
是防止GHC浮出函数体所必需的,thereby recovering sharing.或者,可以使用{-# OPTIONS -fno-full-laziness #-}
编译并使用简单的lambda和应用程序而不是上述机器。
答案 1 :(得分:3)
在像发布者这样的指称式解释器中,我说的是渴望与懒惰的语义,而不是CBV / CBN。当然,渴望对应于CBV,而对CBN则是懒惰的。
由于Haskell很懒,代码
interpret env (App e1 e2) = case interpret env e1 of
V _ -> error "not a function"
F f -> f (interpret env e2)
实现了一个惰性语义(CBN)。 (正如luqui所说,GHC会按照需要的方式减少这种情况。)
要获得急切(CBV)语义,我们可以在调用之前强制参数:
interpret env (App e1 e2) = case interpret env e1 of
V _ -> error "not a function"
F f -> case interpret env e2 of
V v -> f v
F g -> f g
这确保没有未评估的thunk被提供给函数,除非它们已经在环境中。但是,仅在评估lambda时填充环境,该lambda将在环境中插入值v
,g
。因此,那里不会插入任何thunk。