为什么Haskell的Data.List中的`transpose`函数不使用`head`和`tail`?

问题描述 投票:2回答:1

我刚刚花了一些时间解决这个问题,我需要以某种方式翻译列表清单。成功解决之后,我想到了这个解决方案:

translate :: [[a]] -> [[a]]
translate ([]:xss) = []
translate xss      = (map head xss) : (translate $ map tail xss)

不久之后,我意识到我只是在尝试转置矩阵 ......进行这样的常规操作。”因此,我决定进行检查,毫不奇怪,我发现Data.List模块包含transpose函数。

但是实际上令我惊讶的是它的定义方式:

transpose               :: [[a]] -> [[a]]
transpose []             = []
transpose ([]   : xss)   = transpose xss
transpose ((x:xs) : xss) = (x : [h | (h:_) <- xss]) : transpose (xs : [ t | (_:t) <- xss])

[它使用列表推导而不是headtail,我认为这很有趣,但不知道为什么这样做。

有没有理由使用列表理解而不是预先定义的headtail函数(使用map,这就是我对函数的处理方式?与代码的效率有关吗?

我不一定对Haskell陌生,但我也不是专家。话虽如此,更多技术答案和解释也将不胜感激,因为我希望了解有关该语言发展的复杂性(即使我现在不了解原因,我也将知道必须研究)。

list haskell transpose nested-lists definition
1个回答
7
投票

对于列表,headtail之类的功能将出错。请注意,如果您编写:

[h | (h:_) <- xss]

那么这等于not等效于map head xss。实际上,上面的列表理解等效于:

let ok (h:_) = pure h
    ok _ = fail "…"
in xss >>= ok

因此,如果模式匹配失败,则我们返回fail ""值。对于列表,这是空列表:

Prelude> fail "" :: [Int]
[]

这对于我们要转置的非矩形列表很重要,例如:

Prelude Data.List> transpose [[1,4,2,5],[1,3], [1,9,5,8]]
[[1,1,1],[4,3,9],[2,5],[5,8]]

它将进行转换:

[ 1 4 2 5]
[ 1 3 ]
[ 1 9 5 8]

至:

[1 1 1]
[4 3 9]
[2 5]
[5 8]

如果某人最终打算在第三行中计算headtail时最终使用headtail,它将崩溃在[1,3]列表上:

Prelude Data.List> transpose' [[1,4,2,5],[1,3], [1,9,5,8]]
[[1,1,1],[4,3,9],[2,*** Exception: Prelude.head: empty list
© www.soinside.com 2019 - 2024. All rights reserved.