2

我是haskell的新手,我正在尝试学习haskell。我正在尝试以我能想到的最可能的方式创建简单的功能,例如“复制”。我已经用模式匹配和守卫做了“复制”。我无法在列表理解中使用 let 。我可以想象让列表理解并不理想,但我更感兴趣为什么它不起作用=]。

我的问题是:为什么 duplicate' 会产生 [Int] 而 duplicate'' 会产生 [[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
4

2 回答 2

5

<-并且let只是意味着不同的东西。

当您编写 时y <- [x,x],您是在说“依次给出y列表中的每个值”。[x,x]

当你写作时let y = [x,x],你是在说“给予y价值[x,x]”。

于 2013-09-21T20:34:04.063 回答
4

let除了定义一个新符号来获取某个给定值之外,什么也不做。您总是可以手动内联定义:

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

任何只有一个<-的表达式因此具有形式

[ f x | x<-xs ]

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

要将这些嵌套列表合并回“平面”列表,您可以利用列表是单子这一事实:

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

Prelude Control.Monad> 加入 [[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 }是一个单子推导,列表推导的泛化。它可以重写[x | a<-q, b<-q, ...]。对于我们的问题,

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

这是你开始的地方。<-使用纯列表推导式执行此操作时,不可避免地会使用两个s。

当然,你仍然可以任何时候使用 let ......

[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]
     ≡ ...
于 2013-09-21T21:10:19.683 回答