可以"绑定"在List monad上做一个减少?

时间:2012-06-08 22:04:10

标签: functional-programming monads

我知道如何只使用“绑定”操作与列表monad相同的Scheme(或Python)mapfilter函数。

这里有一些Scala来说明:

scala> // map
scala> List(1,2,3,4,5,6).flatMap {x => List(x * x)}                        
res20: List[Int] = List(1, 4, 9, 16, 25, 36)

scala> // filter    
scala> List(1,2,3,4,5,6).flatMap {x => if (x % 2 == 0) List() else List(x)}
res21: List[Int] = List(1, 3, 5)

在Haskell中也是如此:

Prelude> -- map
Prelude> [1, 2, 3, 4, 5, 6] >>= (\x -> [x * x])
[1,4,9,16,25,36]

Prelude> -- filter
Prelude> [1, 2, 3, 4, 5, 6] >>= (\x -> if (mod x 2 == 0) then [] else [x])
[1,3,5]

Scheme和Python也有一个reduce函数,通常与mapfilter组合在一起。 reduce函数使用提供的二进制函数组合列表的前两个元素,然后将结果与下一个元素组合,然后依此类推。计算值列表的总和或乘积的常用用法。这里有一些Python来说明:

>>> reduce(lambda x, y: x + y, [1,2,3,4,5,6])
21
>>> (((((1+2)+3)+4)+5)+6)
21

有没有办法只使用列表monad上的绑定操作来执行此reduce的等效操作?如果bind不能单独执行此操作,那么执行此操作的最“monadic”方式是什么?

如果可能,请在回答时限制/避免使用语法糖(即:Haskell中的do符号或Scala中的序列理解)。

2 个答案:

答案 0 :(得分:2)

绑定操作的一个定义属性是结果仍然在monad¹“内部”。因此,当您对列表执行绑定时,结果将再次成为列表。由于reduce操作²常常导致列表以外的其他内容,因此无法用绑定操作表示。

除此之外,列表上的绑定操作(即concatMap / flatMap)一次只查看一个元素,并且无法重用前面步骤的结果。因此,即使我们将结果包装在单个元素列表中也没问题,但是单独使用monad操作就无法做到这一点。


¹因此,如果你有一个类型允许你不对它执行任何操作,除了由monad类型类定义的类型,你永远不会“打破”monad。这就是使IO monad工作的原因。

²顺便说一下,在Haskell和Scala中称为fold。

答案 1 :(得分:2)

  

如果bind不能单独执行此操作,那么执行此操作的最“monadic”方式是什么?

虽然@ sepp2k给出的答案是正确的,但有一种方法可以在列表上单独执行reduce - 类操作,但使用产品或“writer”monad以及与通过 list functor 分发 产品monad

定义是:

import Control.Monad.Writer.Lazy
import Data.Monoid

reduce :: Monoid a => [a] -> a
reduce xs = snd . runWriter . sequence $ map tell xs 

让我打开包装:

  • Writer monad的数据类型为Writer w a,它基本上是值a和“已写入”值w的元组(产品)。写入值w的类型必须是 monoid ,其中Writer monad的 bind 操作定义如下:

        (w, a) >>= f = let (w', b) = f a in (mappend w w', b)
    

    即。获取传入的写入值和结果写入值,并使用monoid的二进制操作将它们组合起来。

  • tell操作会写入一个值tell :: w -> Writer w ()。因此map tell具有类型[a] -> [Writer a ()],即monadic值列表,其中原始列表的每个元素都已在monad中“写入”。

  • sequence :: Monad m => [m a] -> m [a]对应于列表和monad之间的分配法,即在列表类型上分配monad类型; sequence可以用bind来定义:

    sequence [] = return []
    sequnece (x:xs) = x >>= (\x' -> (sequence xs) >>= (\xs' -> return $ x':xs'))
    

    actually the implementation in Prelude uses foldr,一种类似减少用法的线索)

    因此,sequence $ map tell xs的类型为Writer a [()]

  • runWriter操作解包Writer类型runWriter :: Writer w a -> (a, w), 这里用snd来计算累计值。

Int列表的示例用法是使用monoid实例:

  instance Monoid Int where
              mappend = (+)
              mempty = 0

然后:

  > reduce ([1,2,3,4]::[Int])
  10