因此,我们有以下数据类型:data Expr = Num Double | Add Expr Expr | Var Char deriving Show
和type Env = [(Char, Double)]
个
此外,我们有这两个功能:
evalDo :: Env -> Expr -> Maybe Double
evalDo _ (Num a) = return a
evalDo e (Var c) = getvar e c
evalDo e (Add a b) = do
a' <- evalDo e a
b' <- evalDo e b
Just (a'+b')
和
getvar :: Env -> Char -> Maybe Double
getvar [] c = Nothing
getvar ((x,y):es) c
| c == x = Just y
|otherwise = getvar es c
因此,如果我们运行以下代码:evalDo [('x', 2)] (Add (Num 5) (Var 'x'))
,我们将收到:Just 7.0
。而evalDo [('x', 2)] (Add (Num 5) (Var 'y'))
的计算结果为Nothing
。
根据我对前面代码的理解,我们的函数与evalDo e (Var c) = getvar e c
行匹配。从那里它调用getvar e c
,结果是Just 2
。然后我们返回到最初的第二次调用,即(Add (Num 5) (Var 'x'))
现在Var 'x'被分解成Just 2
,所以最后evalDo e (Add a b) = do...
函数行匹配,我们得到Just7.0。
显然,我的解释有一个巨大的缺陷,因为我们试图将Num5和Just 2“相加”,而根据我们的定义,这是不可能的。然而,这个函数似乎在某种程度上毫无瑕疵地工作,但我就是想不通。
2条答案
按热度按时间gywdnpxw1#
让我们把它分解成几个步骤。首先,
do
符号在Haskell中并不神奇,它对普通的运算符来说是一个反符号。让我们看看evalDo
中的最后一个例子。通过the destructuring rules,这相当于
因此,当我们调用
evalDo [('x', 2)] (Add (Num 5) (Var 'x'))
时,我们 * 首先 * 匹配Add a b
行,我们将用a = Num 5
和b = Var 'x'
来做这个例子,在这个分支中,我们同时使用a
和b
。但是我们 * 不 * 将结果赋给变量。如果我们使用
let
,那么a'
将等于Just 5
,b'
将等于Just 2
。但实际情况并非如此。我们使用<-
,它将简化为一元绑定。让我们来看看第一个evalDo
。它看起来像这样。evalDo
部分返回Just 5
。Maybe
上的>>=
运算符(通常读作“bind”)定义为如果左边的值为
Just
,那么右边的函数将以Just
的 inside 作为参数被调用;如果左边的值为Nothing
,那么右边的函数将 *never call *。在我们的例子中,
evalDo e a
的计算结果是Just 5
,所以我们调用参数为5
的lambda函数,lambda函数调用它的参数a'
,所以我们得到a'
等于5
,5
在Just
的内部。第二种情况也是一样的,用
Just 2
代替Just 5
。如果我们给它一个不存在的变量名,那么我们会得到一个Nothing
,lambda永远不会被调用,整个函数会返回Nothing
。do
符号只是让所有这些高阶函数变得毫无意义。所以我们不用想那么多。回到这里,通常应该这样理解:“对
a
求值,对b
求值,并返回a' + b'
“。<-
绑定带有一些上下文。在您的例子中,上下文是通过Maybe
失败的,但原则上它可以是一个局部变量,非确定性,这是通过lambda实现的,但是在用Haskell编程和写do
表示法的过程中,你不会这样想。jjjwad0x2#
我认为您缺少的部分是
<-
不仅仅是一个赋值运算符;它会“打开”Just
值,并将找到的值绑定到a'
和b'
。让我们看看
Maybe
类型的>>=
。如果
evalDo e a
产生Nothing
,我们可以忽略do
块的其余部分,直接计算Nothing
,但如果它是Just 5
,我们将5(而不是Just 5
)绑定到a'
并继续,同样的事情也发生在evalDo e b
:要么我们得到Just 2
,将2绑定到b'
,然后继续,要么立即返回Nothing
。只有当 * 两个 * 递归调用都产生
Just
值时,我们才能到达最后一行,在这里我们可以添加5 + 2
并将结果 Package 在Just
中。