我知道如何只使用“绑定”操作与列表monad相同的Scheme(或Python)map
和filter
函数。
这里有一些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
函数,通常与map
和filter
组合在一起。 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中的序列理解)。
答案 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