在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?
我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。
在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?
我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。
当前回答
Monad是一种带有特殊机器的盒子,它允许你从两个嵌套的盒子中制作一个普通的盒子,但仍然保持两个盒子的一些形状。
具体来说,它允许您执行连接,类型为Monad m=>m(m a)->m a。
它还需要一个返回操作,它只包装一个值。return::Monad m=>a->m a你也可以说joinunboxes和return wrappes,但join不是Monad m=>m a->a类型的(它不会打开所有Monad,而是打开Monad,Monad在其中)
所以它取一个Monad盒子(Monad m=>,m),里面有一个盒子((m a)),然后生成一个普通盒子(m a。
然而,Monad通常用于(>>=)(口语“bind”)运算符,它本质上只是一个fmap和一个接一个的join。具体而言,
x >>= f = join (fmap f x)
(>>=) :: Monad m => (a -> m b) -> m a -> m b
请注意,函数出现在第二个参数中,而不是fmap。
此外,join=(>>=id)。
为什么这有用?本质上,它允许您在某种框架(Monad)中工作时制作将动作串在一起的程序。
Haskell中Monad的最突出用途是IO Monad。现在,IO是对Haskell中的Action进行分类的类型。在这里,Monad系统是唯一的保存方式(华丽的词):
参考透明度懒惰纯洁
本质上,像getLine::IOString这样的IO操作不能被String替换,因为它总是具有不同的类型。把IO想象成一种神奇的盒子,可以把东西传送给你。然而,仍然只是说getLine::IOString和所有函数都接受IOa会导致混乱,因为可能不需要这些函数。const“üp§”getLine会做什么?(const丢弃第二个参数。const a b=a。)getLine不需要求值,但应该执行IO!这使得行为相当不可预测,也使得类型系统不那么“纯粹”,因为所有函数都将采用a和IOa值。
输入IO Monad。
要将动作串在一起,只需展平嵌套的动作。要将函数应用于IO操作的输出,IO a类型中的a,只需使用(>>=)。
例如,输出输入的行(输出行是一个生成IO操作的函数,匹配右参数>>=):
getLine >>= putStrLn :: IO ()
-- putStrLn :: String -> IO ()
这可以用do环境更直观地写出来:
do line <- getLine
putStrLn line
本质上,这样的do块:
do x <- a
y <- b
z <- f x y
w <- g z
h x
k <- h z
l k w
…转化为:
a >>= \x ->
b >>= \y ->
f x y >>= \z ->
g z >>= \w ->
h x >>= \_ ->
h z >>= \k ->
l k w
还有m>>=\_->f的>>运算符(当框中的值不需要在框中创建新框时)也可以写成a>>b=a>>=constb(consta b=a)
此外,返回运算符是根据IO直觉建模的-它返回一个具有最小上下文的值,在这种情况下没有IO。由于IO a中的a表示返回的类型,这类似于命令式编程语言中的return(a),但它不会停止操作链!f>>=return>>=g与f>>=g相同。仅当您返回的术语在链中较早创建时才有用-请参见上文。
当然,还有其他Monad,否则它不会被称为Monad,它会被称为“IO控制”之类的东西。
例如,List Monad(Monad[])通过串联变平-使(>>=)运算符对列表的所有元素执行函数。这可以被视为“不确定性”,其中列表是许多可能的值,而Monad框架正在进行所有可能的组合。
例如(GHCi):
Prelude> [1, 2, 3] >>= replicate 3 -- Simple binding
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> concat (map (replicate 3) [1, 2, 3]) -- Same operation, more explicit
[1, 1, 1, 2, 2, 2, 3, 3, 3]
Prelude> [1, 2, 3] >> "uq"
"uququq"
Prelude> return 2 :: [Int]
[2]
Prelude> join [[1, 2], [3, 4]]
[1, 2, 3, 4]
因为:
join a = concat a
a >>= f = join (fmap f a)
return a = [a] -- or "= (:[])"
如果出现这种情况,“也许莫纳德”只会将所有结果作废为“无”。也就是说,绑定自动检查函数(a>>=f)是否返回或值(a>>>=f)是否为Nothing,然后也返回Nothing。
join Nothing = Nothing
join (Just Nothing) = Nothing
join (Just x) = x
a >>= f = join (fmap f a)
或者更明确地说:
Nothing >>= _ = Nothing
(Just x) >>= f = f x
State Monad用于同时修改某些共享状态-s->(a,s)的函数,因此>>=的参数为:a->s->(a,s)。这个名称有点用词不当,因为State实际上是用于状态修改功能,而不是用于状态——状态本身确实没有有趣的财产,它只是被改变了。
例如:
pop :: [a] -> (a , [a])
pop (h:t) = (h, t)
sPop = state pop -- The module for State exports no State constructor,
-- only a state function
push :: a -> [a] -> ((), [a])
push x l = ((), x : l)
sPush = state push
swap = do a <- sPop
b <- sPop
sPush a
sPush b
get2 = do a <- sPop
b <- sPop
return (a, b)
getswapped = do swap
get2
那么:
Main*> runState swap [1, 2, 3]
((), [2, 1, 3])
Main*> runState get2 [1, 2, 3]
((1, 2), [1, 2, 3]
Main*> runState (swap >> get2) [1, 2, 3]
((2, 1), [2, 1, 3])
Main*> runState getswapped [1, 2, 3]
((2, 1), [2, 1, 3])
也:
Prelude> runState (return 0) 1
(0, 1)
其他回答
实际上,monad是函数组合运算符的一种自定义实现,它考虑了副作用以及不兼容的输入和返回值(用于链接)。
http://mikehadlow.blogspot.com/2011/02/monads-in-c-8-video-of-my-ddd9-monad.html
这是你要找的视频。
用C#演示组合和对齐类型的问题,然后用C#正确实现它们。最后,他展示了F#和Haskell中相同的C#代码的外观。
monad是用于封装状态变化的对象的东西。在不允许您具有可修改状态的语言(例如,Haskell)中最常遇到这种情况。
例如文件I/O。
您将能够使用文件I/O的monad来将不断变化的状态本质与使用monad的代码隔离开来。Monad内部的代码可以有效地忽略Monad外部世界的变化状态,这使您更容易理解程序的整体效果。
你应该首先了解函子是什么。在此之前,先了解高阶函数。
高阶函数只是一个以函数为自变量的函数。
函子是任何类型构造T,其中存在一个高阶函数,称之为map,它将类型为A->b的函数(给定任意两个类型A和b)转换为函数Ta->Tb。该map函数还必须遵守恒等式和复合法则,以便以下表达式对所有p和q返回true(Haskell表示法):
map id = id
map (p . q) = map p . map q
例如,名为List的类型构造函数是一个函子,如果它配备了一个类型为(a->b)->Lista->Listb的函数,该函数遵守上述定律。唯一实际的实施是显而易见的。生成的Lista->Listb函数在给定列表上迭代,为每个元素调用(a->b)函数,并返回结果列表。
monad本质上只是一个函子T,它有两个额外的方法,类型为T(T A)->T A的join和类型为A->T A的unit(有时称为return、fork或pure)。对于Haskell中的列表:
join :: [[a]] -> [a]
pure :: a -> [a]
为什么有用?因为例如,您可以使用返回列表的函数映射列表。Join获取生成的列表列表并将它们连接起来。列表是monad,因为这是可能的。
您可以编写一个函数,先映射,然后连接。此函数称为bind或flatMap,或(>>=)或(=<<)。这通常是Haskell中给出monad实例的方式。
monad必须满足某些定律,即联接必须是关联的。这意味着,如果您的值x类型为[[a]]],那么join(join x)应该等于join(map joinx)。纯必须是联接的标识,这样联接(纯x)==x。
我最喜欢的Monad教程:
http://www.haskell.org/haskellwiki/All_About_Monads
(在谷歌搜索“monad教程”的17万次点击中!)
@斯图:monads的目的是允许您将(通常)顺序语义添加到纯代码中;您甚至可以组合Monad(使用Monad Transformers)并获得更有趣和复杂的组合语义,例如,带有错误处理的解析、共享状态和日志记录。所有这些在纯代码中都是可能的,monad只允许您将其抽象并在模块化库中重用(在编程中总是很好的),并提供方便的语法使其看起来势在必行。
Haskell已经有了运算符重载[1]:它使用类型类的方式与使用Java或C#中的接口的方式非常相似,但Haskell恰好也允许使用非字母数字标记(如+&&和>)作为中缀标识符。如果您的意思是“重载分号”[2],那么在您看来这只是运算符重载。“重载分号”听起来像是黑魔法,自找麻烦(想象一下有进取心的Perl黑客听到了这个想法),但关键是没有monad就没有分号,因为纯函数代码不需要或不允许显式排序。
这一切听起来比实际情况要复杂得多。sigfpe的文章很酷,但使用了Haskell来解释它,这有点无法打破理解Haskell到grok Monads和理解Monads到grok Haskell的鸡和蛋的问题。
[1] 这是与monad不同的问题,但monad使用Haskell的运算符重载特性。
[2] 这也是一个过度简化,因为链接一元操作的运算符是>>=(发音为“bind”),但有语法糖(“do”)允许您使用大括号和分号和/或缩进和换行。