为什么你不能在Haskell中使用没有'let..in'块的'Just'语法?

我有几个关于JustHaskell 语法的问题。

当我尝试用不同的方法编写函数来计算二项式系数时出现问题。

考虑函数:

binom :: Integer -> Integer -> Maybe Integer
binom n k | n < k     = Nothing
binom n k | k == 0    = Just 1
binom n k | n == k    = Just 1
binom n k | otherwise = let 
                          Just x = (binom (n-1) (k-1))
                          Just y = (binom (n-1) k)
                        in
                          Just (x + y)

当我尝试otherwiselet..in没有 let..in 块的情况下编写没有块的情况时,如下所示:

binom n k | otherwise = (binom (n-1) (k-1)) + (binom (n-1) k)

我面临编译错误No instance for (Num (Maybe Integer)) arising from a use of ‘+’。所以我的第一个想法是我忘记了Just语法,所以我把它改写为

binom n k | otherwise = Just ((binom (n-1) (k-1)) + (binom (n-1) k))
binom n k | otherwise = Just ((binom (n-1) (k-1)) + (binom (n-1) k))

我面临一个更令人困惑的错误:

Couldn't match type ‘Maybe Integer’ with ‘Integer’
      Expected: Maybe Integer
        Actual: Maybe (Maybe Integer)

如果我Just在 binom 调用之前添加,错误只会复合:

Couldn't match type ‘Maybe (Maybe Integer)’ with ‘Integer’
      Expected: Maybe Integer
        Actual: Maybe (Maybe (Maybe Integer))

此外,如果我写:

Just x = binom 3 2
y = binom 3 2

x会有价值3y也会有价值Just 3

所以我的问题是:

  1. 为什么语法需要let..in块才能正确编译?
  2. 在函数中,为什么不使用时Just添加Maybe类型let..in
  3. 相反,如果Just函数Just的类型为Just :: a -> Maybe a

奖金问题,但不相关:

  • 当我声明没有类型的函数时,编译器会推断类型binom :: (Ord a1, Num a2, Num a1) => a1 -> a1 -> Maybe a2。现在我大致了解这里发生了什么,但我不明白为什么a1有两种类型。

回答

您的问题展示了您可能对正在发生的事情感到困惑的几种方式。

首先,Just它不是任何一种语法——它只是标准库提供的一个数据构造函数(因此也是一个函数)。因此,您的失败尝试未编译的原因不是由于任何语法错误(在这种情况下编译器会报告“解析错误”),而是 - 正如它实际报告的那样 - 类型错误。换句话说,编译器能够解析代码以理解它,但是在检查类型时,意识到有些事情发生了。

因此,为了扩展您失败的尝试,#1 是这样的:

报告的错误是

No instance for (Num (Maybe Integer)) arising from a use of ‘+’

这是因为您试图将 2 次调用的结果添加到binom- 根据您的类型声明,是 type 的值Maybe Integer。而且 Haskell 默认不知道如何添加两个Maybe Integer值(会Just 2 + Nothing是什么?),所以这不起作用。您需要 - 正如您最终成功尝试所做的那样 - 解开底层的 Integer 值(假设它们存在!我稍后会回到这个问题),将它们相加,然后将结果总和包装在Just.

我不会详述其他失败的尝试,但希望您能看到,在各种方面,这些类型也无法以编译器描述的方式在这里匹配。在 Haskell 中,您真的必须了解类型,而只是随意地抛开各种语法和函数调用,希望最终能够编译,这会导致挫折和失败!

所以对于你明确的问题:

为什么语法需要 let..in 块才能正确编译?

它没有。它只需要在任何地方匹配的类型。你最终得到的版本:

let 
  Just x = (binom (n-1) (k-1))
  Just y = (binom (n-1) k)
in
  Just (x + y)

很好(从类型检查的角度来看,无论如何!)因为您正在按照我之前描述的方式进行 - 从Just包装器中提取基础值(这些是xy),将它们相加并重新包装它们。

但这种方法是有缺陷的。一方面,它是样板文件 - 如果您是第一次看到它,需要编写大量代码并尝试理解它,而底层模式非常简单:“解开值,将它们加在一起,然后重新包装”。所以应该有一种更简单、更容易理解的方法来做到这一点。还有,使用 Applicative 类型类的方法 - 该Maybe类型是其中的成员。

有经验的 Haskellers 会以两种方式之一编写上述内容。任何一个:

binom n k | otherwise = liftA2 (+) (binom (n-1) (k-1)) (binom (n-1) k)

或者

binom n k | otherwise = (+) <$> binom (n-1) (k-1) <*> binom (n-1) k

(在所谓的“应用性风格”后者-如果你不熟悉应用型函子有一个在学习你Haskell的一个伟大的介绍在这里。)

与您的方式相比,这样做还有另一个优势,除了避免样板代码。let... in表达式中的模式匹配假定等的结果binom (n-1) (k-1)的形式为Just x。但它们也可能是Nothing- 在这种情况下,您的程序将在运行时崩溃!正如@chepner 在他的回答中所描述的那样,这确实会发生在您的情况下。

由于 Applicative 实例的实现方式,使用liftA2<*>Maybe避免崩溃,只要Nothing您尝试添加的内容之一是 Nothing 就可以避免崩溃。(这反过来意味着您的函数将始终返回Nothing- 我会让您自行找出解决方法!)

我不确定我是否真的理解你的问题 #2 和 #3,所以我不会直接解决这些问题 - 但我希望这能让你对如何Maybe在 Haskell 中工作有更多的了解。最后,对于您的最后一个问题,尽管它很不相关:“我不明白为什么 a1 有两种类型”-它没有。a1表示单一类型,因为它是单一类型变量。您大概指的是它有两个约束- hereOrd a1Num a1. OrdNum这里是类型类 - 就像我之前提到的 Applicative 一样(尽管 Ord 和 Num 是更简单的类型类)。如果您不知道什么是类型类,我建议您先阅读介绍性资源,例如 Learn You a Haskell,然后再继续使用该语言 - 但简而言之,它有点像一个接口,说明类型必须实现某些功能。具体来说,Ord说类型必须实现顺序比较——你在这里需要它,因为你已经使用了<运算符——而Num说你可以用它做数字事情,比如加法。因此,该类型签名只是明确了您的函数定义中隐含的内容 - 您使用此函数的值必须是同时实现顺序比较和数字运算的类型。


以上是为什么你不能在Haskell中使用没有'let..in'块的'Just'语法?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>