【问题标题】:How does mapM work with const functions in Haskell?mapM 如何与 Haskell 中的 const 函数一起工作?
【发布时间】:2020-10-26 16:17:32
【问题描述】:

当我一直在寻找优化我一直在制作的密码破解器的方法时,我遇到了一个列表中所有可能的字符组合的更短的实现,它使用了这个函数:

mapM (const xs) [1..n]

其中xs 可以是可用的字符,n 可以是所需单词的长度。 所以

mapM (const "abcd") [1..4]

会输出一个列表["aaaa","aaab","aaac","aaad","aaba","aabb"..] 等等。只有长度对右边的列表很重要,我可以写 ['f','h','s','e'] 或任何 4 元素列表。

我明白为什么列表无关紧要,它被传递给const 函数。我可以看到列表中的const 在技术上满足(a -> m a)

但我的问题是:为什么输出不是简单的["abcd","abcd","abcd","abcd"],或者"abcdabcdabcdabcd"const 函数如何输出给定字母的所有 4 个字母变体?

【问题讨论】:

  • 你的问题的答案在stackoverflow.com/a/61590761/1248563
  • 等效方法:replicateM 4 "abcd".
  • 列表是MonadmapM 的一个实例。但是,您可以在此处使用replicateM:例如replicateM 4 ['a' .. 'z']

标签: haskell constants combinations monads map-function


【解决方案1】:

你可以用这种直觉理解mapM

mapM f [x1, x2, ..., xN]
=
do y1 <- f x1
   y2 <- f x2
   ...
   yN <- f xN
   return [y1, y2, ..., yN]

在你的情况下:

mapM (const "abcd") [1 .. 4]
=
do y1 <- const "abcd" 1
   y2 <- const "abcd" 2
   y3 <- const "abcd" 3
   y4 <- const "abcd" 4
   return [y1, y2, y3, y4]
=
do y1 <- "abcd"
   y2 <- "abcd"
   y3 <- "abcd"
   y4 <- "abcd"
   return [y1, y2, y3, y4]

后者等价于列表推导

[ [y1, y2, y3, y4] | y1<-"abcd", y2<-"abcd", y3<-"abcd", y4<-"abcd"]

这将产生你的输出。

【讨论】:

  • 我以前从未见过带有列表的 do 表示法。一开始有点反直觉,但最后你会意识到这太棒了!!!
【解决方案2】:

(此答案假定traverse 而不是mapM,但这两个功能在很大程度上是等效的。mapM 的存在主要是出于历史原因。)

可以从两个不同的角度来看 Haskell 列表。一方面,它们是包含值的数据结构。另一方面,它们代表非确定性效应。非确定性不是在生成随机值的意义上,而是在具有多个可能的值替代的意义上

现在,假设我们有两个“不确定的Ints”(即两个Ints 列表)。如果我们想对它们求和怎么办?将两个不确定的Ints 相加意味着什么?

答案由[]Applicative 实例提供,特别是liftA2 函数,它允许我们将结合Ints 的函数提升为结合非确定性Ints 的函数。给它一个比它真正拥有的更具体的签名:

liftA2 ::  (Int -> Int -> Int) -> [Int] -> [Int] -> [Int]

但是它有什么作用呢?基本上它将函数应用于所有可能的组合

ghci> liftA2 (+) [1,2] [5,7]
[6,8,7,9]

还有一个liftA3 对三元函数和三个非确定性值执行相同的操作。

现在,如果我们有一个完整的常规值容器,并用一个返回非确定性值的函数映射它呢?我们需要组合容器中 all 元素的生成值,而不仅仅是像 liftAX 函数那样的两个或三个。有一个名为traverse 的不同函数可以完成这项工作。它仅适用于某些容器,即具有 Traversable 实例的容器。

这可能是造成混乱的根源。在您的示例中,列表既可以用作效果(Applicative 的实例),也可以用作可以使用有效函数映射的容器(Traversable 的实例)。

为了减少混淆,让我们创建自己的可遍历容器,不同于[]

data Triad a = Triad a a a deriving (Show,Functor,Foldable,Traversable)

并像示例中一样调用traverse

traverse (const "abcd") (Triad 1 2 3)

我们得到了什么?比如:

[Triad 'a' 'a' 'a',Triad 'a' 'a' 'b',Triad 'a' 'a' 'c',...

可以将其视为Triads 的列表,也可以视为通过使用Applicative 实例组合const "abcd" 1const "abcd" 2const "abcd" 3 的非确定性影响而产生的非确定性Triad [].

注意:因为Triad 总是有三个组件,这相当于liftA3 Triad (const "abcd" 1) (const "abcd" 2) (const "abcd" 3) 使用liftA3

它也等同于使用sequenceAsequenceA (Triad (const "abcd" 1) (const "abcd" 2) (const "abcd" 3))sequenceA 适用于元素已经是“nodetermimistic values”的容器。

【讨论】:

    【解决方案3】:
    mapM (const xs) [1..n]
     ==
    sequence $ map (const xs) [1..n]
     ==
    sequence [xs | _ <- [1..n]]
     ==
    sequence $ replicate n xs
     ==
    replicateM n xs
    

    根据定义

    sequence [xs | _ <- [1..n]]        -- n >= 1
     ==
    [(x:ys) | x <- xs ,
              ys <- sequence [xs | _ <- [1..n-1]]]
    

    因此

    sequence [xs | _ <- [1..n]]
     ==
    [ [x1,x2,...,xn] | x1 <- xs, x2 <- xs, ..., xn <- xs]
    

    所以赋予它特殊意义的不是const函数,而是Monad对于列表的定义,由此引出了sequence对于列表的含义。

    Monads 可以说是generalized nested loops(还有很多其他东西),而list monad 就是这样,nested loops,没有一概而论。可以在上面的列表理解图中看到。

    对于列表,do 表示法和列表推导是相同的。对于任何 monad,甚至还有 monad 推导式,看起来像列表推导式。 do&lt;- 只是符号,可以写成 forin 一样。想象一下

    for x1 in "abcd":
      for x2 in "abcd":
         for x3 in "abcd":
            yield [x1,x2,x3]
    

    作为 有效的 Haskell

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-30
      • 1970-01-01
      • 2021-07-01
      • 1970-01-01
      • 1970-01-01
      • 2015-10-13
      • 1970-01-01
      相关资源
      最近更新 更多