Haskell列表连接推断类型

时间:2013-04-18 17:24:00

标签: list haskell inferred-type

尝试使用新元素替换给定点的列表中的元素,然后返回该元素。

 setelt :: Int -> [a] -> a -> [a]
 setelt x (yf:(y:yl)) z
   | x == (length yf) = (yf:(z:yl))

导致此错误:

Inferred type is not general enough
Expression    : setelt
Expected type : Int -> [a] -> a -> [a]
Inferred type : Int -> [[a]] -> [a] -> [[a]]

yf:y:yl的连接似乎没有问题,因此不确定如何解决。

3 个答案:

答案 0 :(得分:8)

您似乎误解了列表构造函数(:)的作用。 Haskell列表是以空列表(:)结尾的[]构造函数序列,模式匹配只是以相同的顺序对这些构造函数进行反汇编。

因此,当您在(yf:(y:yl))上进行模式匹配时,您真正匹配的是至少两个元素的列表,yfy以及yl为列表的其余部分。

推断类型不是您所期望的,因为length yf暗示yf - 这是输入列表的第一个元素 - 本身就是一个列表。

您需要做的是递归地沿着列表向下走,使用输入的当前元素或替换元素x在到达正确位置时构建新列表。一般表单看起来应该像标准库函数map,它实现如下:

map _ [] = []
map f (x:xs) = f x : map f xs

除非您需要一种方法来跟踪您正在搜索的索引,而不是转换每个元素。

如果应用于0或1个元素的列表,您当前的函数也会失败,但在整体修改算法后,修复它应该很容易。

答案 1 :(得分:0)

阅读C. A. McCann的答案,以获得更多的洞察力,特别是对于列表太短的问题。通过修改算法,您可以通过以下简单的修改来修复代码:

 setelt :: Int -> [a] -> a -> [a]
 setelt x (yf:(y:yl)) z
   | x == (length (y:yl)) = (yf:(z:yl))

可以简单地重写:

 setelt :: Int -> [a] -> a -> [a]
 setelt x (yf:ys@(_:yl)) z
   | x == (length ys) = (yf:(z:yl))

答案 2 :(得分:0)

除了模式匹配的问题(我也推荐C. A. McCann的答案),你的程序可能效率低于预期,当然效率低于预期。

问题在于Haskell的列表是简单的单链表,它们不以方便的O(1)可访问形式携带它们的长度。 Haskell的length必须计算列表节点的数量,这需要O(N)时间。这意味着setelt的直接修正版本(由Nicolas Dudebout的回答提供)将扫描每一步的剩余列表,产生O(N ^ 2)最坏情况的性能,而不是O(N)是可能的。

要解决此问题,请先扫描列表以获取长度。类似于以下实现,即O(N)(尽管使用takedrop最终会扫描列表的时间超过严格必要的次数):

setelt :: Int -> [a] -> a -> [a]
setelt n ys z = front ++ z:back where
  count = length ys - n
  front = take count ys
  (_:back) = drop count ys

最后,如果不清楚:标准Haskell列表索引(由takedrop!!使用)从列表开头的0开始,尾部没有1(看起来可能是setelt的意图,并在上面实现)。如果你的目的是从0开头,那么实现就更容易了:

setelt n ys z = front ++ z:back where
    front = take n ys
    (_:back) = drop n ys

或更有效率:

setelt 0 (y:ys) z = z:ys
setelt n (y:ys) z = y:setelt (n-1) ys z