Haskell长度+地图说明?

时间:2014-05-06 00:52:58

标签: haskell

我正在和Haskell一起玩,因为我正在学习这门语言,而我只是找到了一些我不理解的东西而且我无法找到解释。如果我尝试运行此代码:

map (`div` 0) [1,2,3,4]

我得到除以0的异常,这是预期的。 但是,如果我运行此代码:

length (map (`div` 0) [1,2,3,4])

我得到4!

当我在长度函数中进行映射时,我想知道为什么我没有得到除以0的异常!

3 个答案:

答案 0 :(得分:15)

可以通过以下方式定义maplength函数:

map :: (a -> b) -> [a] -> [b]
map _ [] = []
map f (x:xs) = f x : map f xs

length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs

现在,让我们手动解决为什么你的第二个例子按照它的方式工作。它是这样的:

length (map (`div` 0) (1:2:3:4:[]))
    = length (1 `div` 0 : map (`div` 0) (2:3:4:[]))     -- second map equation
    = 1 + (length (map (`div` 0) (2:3:4:[])))           -- second length equation
    = 1 + (length (2 `div` 0 : map (`div` 0) (3:4:[]))) -- second map equation
    .
    .
    .
    = 1 + (1 + (1 + (1 + length (map (`div` 0) [])))))  -- second length equation
    = 1 + (1 + (1 + (1 + length []))))                  -- first map equation
    = 1 + (1 + (1 + (1 + 0))))                          -- first length equation
    = 4                                                 -- arithmetic

这里的诀窍是什么?在Haskell中,评估表达式的过程称为强制它。强制表达式执行必要的最少工作,以便找出结果的最外层数据构造函数。作为其中的一部分,子表达式将仅在必要时被强制实现目标。

所以在这个例子中,我们强制的最外层表达式是length函数的应用程序。 length的定义有两种情况,一种使用[]列表构造函数,另一种使用(:)构造函数,因此要应用length我们需要弄清楚这两种情况适用于论证。由于参数的最外层位置没有构造函数,因此我们必须强制它查找。这就是上面推导的第一行和第二行之间的步骤;我们通过查看其参数并选择第二个map等式强制map子表达式。

但在此之后,我们获得了确定length的两个方程式中的哪一个所需的所有信息,因此我们按照“最外层的第一”规则并应用适当的length方程。在这种情况下,这将丢弃包含除以零的子表达式,这意味着永远不会强制子表达式,并且永远不会触发错误。

然而,在您的第一个示例中,当您将表达式键入GHCI时,您隐式要求解释器打印其结果。这要求它强制列表的主干访问其元素,并强制元素本身打印它们。因此,当您强制列表的第一个元素时,会发生除零错误。


编辑:让我指出你可能没有注意到的一个细微差别。当我们在GHCI中尝试你的第一个例子时,这是会话的结果:

*Main> map (`div` 0) [1,2,3,4]
[*** Exception: divide by zero

在第二行的开头看到那个孤独的开口方括号?在发生除零错误之前,这是正在打印的列表的左括号。同样,请注意此示例中发生的情况:

*Main> map (20 `div`) [1,2,0,4]
[20,10,*** Exception: divide by zero

结果列表的前两个元素,甚至是将第二个元素与第三个元素分开的逗号,打印成功,因为Haskell在需要打印之前不会尝试计算第三个列表元素。

答案 1 :(得分:5)

如果在解释器中键入map表达式,它将对其进行求值,然后打印结果列表。为此,需要对结果列表中的所有元素进行求值,因为它们将成为显示的字符串的一部分。

但是,当解释器评估length表达式时,它只需要查看结果列表的结构。它不必查看列表中的实际元素。因此,由于Haskell是一种惰性语言,它只评估它所拥有的内容,这意味着不会对元素进行求值,因此不会抛出任何异常。

答案 2 :(得分:5)

这是一些很好的老Haskell懒惰的评价!如果Haskell不需要计算某些东西,它就不会。在这种情况下,您在长度为4的列表上调用map。就Haskell而言,将map应用于任何列表将返回相同大小的列表,无论您是什么操作应用。因此,Haskell只是告诉你长度为4,而实际上没有将任何东西除以0。