首页 文章

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

提问于
浏览
2

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

我的问题是:为什么复制'产生[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 回答

  • 4

    <-let 只是意味着不同的东西 .

    当你写 y <- [x,x] 时,你说的是“依次给列表 [x,x] 中的每个值_3040667” .

    当你写 let y = [x,x] 时,你说“给 y[x,x] ” .

  • 5

    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
    

    前奏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
    

    把它放到 duplicate 的修改后的 let 版本中:

    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理解,列表推导的概括 . 它可以改写 [x | a<-q, b<-q, ...] . 对于我们的问题,

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

    这是你开始的地方 . 使用纯列表理解时,使用两个 <- 是不可避免的 .

    当然你还可以随时使用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]
         ≡ ...
    

相关问题