Haskell:让里面列表理解出乎意料的结果

时间:2013-09-21 20:05:35

标签: haskell list-comprehension let

我是哈斯克尔的新手,我正在努力学习哈斯克尔。我试图以最可能的方式创建像“复制”这样的简单函数。我已经完成了“复制”模式匹配和警卫。 我无法让它在列表理解中使用。 我可以想象让列表理解不是理想的但我更感兴趣的是它为什么不起作用=]。

我的问题是:为什么复制'产生[Int]和复制''产生[[Int]] 甚至可以在列表理解中使用let生成[Int]?

感谢您的时间和帮助:)。

--list comprehension
duplicate' xs = [y | x <- xs, y <- [x,x]]
input => [1,2,3,4]
output => [1,1,2,2,3,3,4,4]
expected? => yes

--list comprehension with let
duplicate'' xs = [y | x <- xs, let y = [x,x]]
input => [1,2,3,4]
output => [[1,1],[2,2],[3,3],[4,4]]
expected? => yes

2 个答案:

答案 0 :(得分:5)

<-let只是意味着不同的事情。

当您撰写y <- [x,x]时,您说&#34;依次为y内的每个值[x,x]提供let y = [x,x]

当您撰写y时,您说&#34;将[x,x]赋予{{1}}&#34;。

答案 1 :(得分:4)

let除了定义一个新符号以取一个给定值之外什么都不做。您可以随时手动内联定义:

[ y | x <- xs, let y = [x,x] ] ≡ [ [x,x] | x <- xs ]

任何只有一个<-的表达式都是

形式
[ f x | x<-xs ]

相当于map f xs。因此,结果列表必须始终与xs具有相同的长度,从而无法实现duplicate的所需行为:如果您想要重复,则需要将它们封装为内部列表,以便赢得算作更多元素。

要将这些嵌套列表合并回“扁平”列表,您可以利用列表是monad的事实:

join :: Monad m => m (m a) -> m a
  

Prelude Control.Monad&gt;加入[[1,1],[2,2]]
  [1,1,2,2]

现在,join实际上是理论家更喜欢定义monad的类别,但正如你可能知道的那样,Haskell的做法有点不同:

(>>=) :: Monad m => m a -> (a -> m b) -> m b
a >>= f = join (fmap f a)

或者,因为它实际上是相反的定义,

join a = a >>= id

将其添加到let的修改后的duplicate版本中:

join [y | x <- xs, let y = [x,x] ]
  ≡ [y | x <- xs, let y = [x,x] ] >>= id
  ≡ map (\x -> [x,x]) xs >>= id
  ≡ xs >>= id . (\x -> [x,x])
  ≡ xs >>= (\x -> [x,x])
  ≡ do { x<-xs; [x,x] }
  ≡ do { x<-xs; y<-[x,x]; return y }  -- by the monad laws

现在,表达式do { a<-p; b<-q; ... return x } monad comprehension ,即列表推导的概括。它可以重写[x | a<-q, b<-q, ...]。对于我们的问题,

join [y | x <- xs, let y = [x,x] ] ≡ [y | x<-xs, y<-[x,x]]

这是你开始的地方。使用纯列表推导时,使用两个<-是不可避免的。

当然,你仍然可以随时使用 一个让...

[y | x<-xs, y<-[x,x]]
     ≡ [y | x<-xs, let z=[x,x], y<-z]
     ≡ [a | x<-xs, let z=[x,x], y<-z, let a=y]
     ≡ [a | x<-xs, let z=let w=[let q=x in q, let r=x in r] in w, y<-z, let a=y]
     ≡ ...