就OOP程序员所能理解的(没有任何功能编程背景)而言,什么是monad?
它解决了什么问题,最常用的地方是什么?
使现代化
为了澄清我所寻求的理解,假设您正在将一个具有monad的FP应用程序转换为OOP应用程序。如何将monad的职责移植到OOP应用程序?
就OOP程序员所能理解的(没有任何功能编程背景)而言,什么是monad?
它解决了什么问题,最常用的地方是什么?
使现代化
为了澄清我所寻求的理解,假设您正在将一个具有monad的FP应用程序转换为OOP应用程序。如何将monad的职责移植到OOP应用程序?
当前回答
典型用法中的Monad在功能上等同于过程编程的异常处理机制。
在现代过程语言中,在一系列语句周围放置一个异常处理程序,其中任何语句都可能引发异常。如果任何语句引发异常,则语句序列的正常执行将停止并转移到异常处理程序。
然而,函数式编程语言在哲学上避免了异常处理特性,因为它们的“goto”性质类似。函数编程的观点是,函数不应该有“副作用”,比如中断程序流的异常。
实际上,由于I/O的原因,在现实世界中不能排除副作用。函数式编程中的monad用于处理这一问题,方法是获取一组链式函数调用(其中任何一个都可能产生意外结果),并将任何意外结果转换为封装数据,这些数据仍然可以安全地通过剩余的函数调用。
控制流被保留,但意外事件被安全地封装和处理。
其他回答
为什么我们需要单子?
我们只想使用函数编程。(毕竟是“功能编程”-FP)。然后,我们遇到了第一个大问题。这是一个程序:f(x)=2*xg(x,y)=x/y我们怎么能说首先要执行什么?我们如何使用不超过个函数来形成一个有序的函数序列(即程序)?解决方案:组合函数。如果你先要g,然后要f,只需写f(g(x,y))。好的,但是。。。更多问题:某些函数可能会失败(即g(2,0),除以0)。我们在FP中没有“例外”。我们如何解决它?解决方案:让我们允许函数返回两种东西:而不是g:Real,Real->Real(函数从两个实数转换为实数),让我们允许g:Real、Real->Real|Nothing(函数从一个实数转换成(实数或零))。但函数应该(更简单地)只返回一件事。解决方案:让我们创建一种要返回的新类型的数据,一种“装箱类型”,它可能包含一个真实的数据,也可能只是一个空数据。因此,我们可以有g:真实,真实->可能真实。好的,但是。。。f(g(x,y))现在发生了什么?f还没有准备好使用“也许真的”。而且,我们不想改变我们可以与g连接的每一个函数,以使用Maybe Real。解决方案:让我们有一个特殊的函数来“连接”/“组合”/“链接”函数。这样,我们就可以在幕后调整一个函数的输出,以支持下一个函数。在我们的例子中:g>>=f(连接/合成g到f)。我们希望>>=获取g的输出,检查它,如果它是Nothing,则不要调用f并返回Nothing;或者相反,提取装箱的实数并用它来馈送f。(此算法只是Maye类型的>>=的实现)。出现了许多其他问题,可以使用相同的模式来解决:1。使用“框”来编码/存储不同的含义/值,并具有像g这样的函数来返回这些“框值”。2.让作曲家/链接器g>>=f帮助将g的输出连接到f的输入,这样我们就不必改变f。使用该技术可以解决的显著问题有:具有函数序列中的每个函数(“程序”)可以共享的全局状态:解StateMonad。我们不喜欢“不纯函数”:对相同输入产生不同输出的函数。因此,让我们标记这些函数,使它们返回一个标记/装箱的值:IOmonad。
完全幸福!!!!
monad是一个函数数组
(Pst:函数数组只是一个计算)。
实际上,这些函数不是真正的数组(一个单元格数组中的一个函数),而是由另一个函数>>=链接。>>=允许调整函数i的结果以馈送函数i+1,并在它们之间执行计算或者甚至不调用函数i+1。
这里使用的类型是“带上下文的类型”。这是一个带有“标记”的值。被链接的函数必须采用“裸值”并返回标记结果。>>=的职责之一是从上下文中提取裸值。还有一个函数“return”,它接受一个裸值并将其与一个标记一起放置。
Maybe的一个例子。让我们使用它来存储一个简单的整数,以便进行计算。
-- a * b
multiply :: Int -> Int -> Maybe Int
multiply a b = return (a*b)
-- divideBy 5 100 = 100 / 5
divideBy :: Int -> Int -> Maybe Int
divideBy 0 _ = Nothing -- dividing by 0 gives NOTHING
divideBy denom num = return (quot num denom) -- quotient of num / denom
-- tagged value
val1 = Just 160
-- array of functions feeded with val1
array1 = val1 >>= divideBy 2 >>= multiply 3 >>= divideBy 4 >>= multiply 3
-- array of funcionts created with the do notation
-- equals array1 but for the feeded val1
array2 :: Int -> Maybe Int
array2 n = do
v <- divideBy 2 n
v <- multiply 3 v
v <- divideBy 4 v
v <- multiply 3 v
return v
-- array of functions,
-- the first >>= performs 160 / 0, returning Nothing
-- the second >>= has to perform Nothing >>= multiply 3 ....
-- and simply returns Nothing without calling multiply 3 ....
array3 = val1 >>= divideBy 0 >>= multiply 3 >>= divideBy 4 >>= multiply 3
main = do
print array1
print (array2 160)
print array3
为了说明monad是带有助手操作的函数数组,请考虑与上述示例等效,仅使用一个实函数数组
type MyMonad = [Int -> Maybe Int] -- my monad as a real array of functions
myArray1 = [divideBy 2, multiply 3, divideBy 4, multiply 3]
-- function for the machinery of executing each function i with the result provided by function i-1
runMyMonad :: Maybe Int -> MyMonad -> Maybe Int
runMyMonad val [] = val
runMyMonad Nothing _ = Nothing
runMyMonad (Just val) (f:fs) = runMyMonad (f val) fs
它的用法如下:
print (runMyMonad (Just 160) myArray1)
monad是一种封装值的数据类型,本质上可以对其应用两个操作:
返回x创建封装x的monad类型的值m>>=f(读作“绑定运算符”)将函数f应用于monad m中的值
这就是monad。还有一些技术问题,但基本上这两个操作定义了monad。真正的问题是,“monad做什么?”,这取决于monad-列表是monad,Maybes是monad;IO操作是monad。当我们说这些东西是monad时,这意味着它们具有返回和>>=的monad接口。
你最近有一篇演讲《Monadologie——关于类型焦虑的专业帮助》(Christopher League,2010年7月12日),这篇演讲对延续和monad的话题非常有趣。这个(幻灯片)演示的视频实际上可以在vimeo上获得。Monad部分开始于37分钟左右,在这段一小时的视频中,从58张幻灯片中的第42张幻灯片开始。
它被称为“函数式编程的主要设计模式”,但示例中使用的语言是Scala,它既是面向对象的又是函数式的。您可以在Debasish Ghosh(2008年3月27日)的博客文章“Monads-在Scala中抽象计算的另一种方法”中阅读更多关于Monad的内容。
如果类型构造函数M支持以下操作,那么它就是monad:
# the return function
def unit[A] (x: A): M[A]
# called "bind" in Haskell
def flatMap[A,B] (m: M[A]) (f: A => M[B]): M[B]
# Other two can be written in term of the first two:
def map[A,B] (m: M[A]) (f: A => B): M[B] =
flatMap(m){ x => unit(f(x)) }
def andThen[A,B] (ma: M[A]) (mb: M[B]): M[B] =
flatMap(ma){ x => mb }
例如(在Scala中):
选项是monad
def unit[A] (x: A): Option[A] = Some(x) def flatMap[A,B](m:Option[A])(f:A =>Option[B]): Option[B] = m match { case None => None case Some(x) => f(x) }
列表为Monad
def unit[A] (x: A): List[A] = List(x) def flatMap[A,B](m:List[A])(f:A =>List[B]): List[B] = m match { case Nil => Nil case x::xs => f(x) ::: flatMap(xs)(f) }
Monad在Scala中非常重要,因为它是为了利用Monad结构而构建的方便语法:
对于Scala的理解:
for {
i <- 1 to 4
j <- 1 to i
k <- 1 to j
} yield i*j*k
由编译器翻译为:
(1 to 4).flatMap { i =>
(1 to i).flatMap { j =>
(1 to j).map { k =>
i*j*k }}}
关键抽象是flatMap,它通过链接绑定计算。flatMap的每次调用都返回相同的数据结构类型(但值不同),作为链中下一个命令的输入。
在上面的代码段中,flatMap将闭包(SomeType)=>List[AanotherType]作为输入,并返回List[Aanother Type]。需要注意的一点是,所有flatMap都采用相同的闭包类型作为输入,并返回与输出相同的类型。
这就是“绑定”计算线程的原因——为了理解,序列中的每一项都必须遵守相同的类型约束。
如果您执行两个操作(可能失败)并将结果传递给第三个,例如:
lookupVenue: String => Option[Venue]
getLoggedInUser: SessionID => Option[User]
reserveTable: (Venue, User) => Option[ConfNo]
但如果不利用Monad,你会得到复杂的OOP代码,比如:
val user = getLoggedInUser(session)
val confirm =
if(!user.isDefined) None
else lookupVenue(name) match {
case None => None
case Some(venue) =>
val confno = reserveTable(venue, user.get)
if(confno.isDefined)
mailTo(confno.get, user.get)
confno
}
而使用Monad,您可以像所有操作一样使用实际类型(地点、用户),并隐藏选项验证内容,这都是因为for语法的平面图:
val confirm = for {
venue <- lookupVenue(name)
user <- getLoggedInUser(session)
confno <- reserveTable(venue, user)
} yield {
mailTo(confno, user)
confno
}
只有当所有三个函数都具有Some[X]时,才会执行屈服部分;任何“无”将直接返回以确认。
So:
Monad允许在函数编程中进行有序计算,这允许我们以一种很好的结构化形式(有点像DSL)对动作序列进行建模。最大的能力来自于将服务于不同目的的monad组合成应用程序中的可扩展抽象的能力。monad对动作的排序和线程化由语言编译器完成,该语言编译器通过闭包的魔力进行转换。
顺便说一句,Monad不是FP中使用的唯一计算模型:
范畴理论提出了许多计算模型。其中计算的Arrow模型莫纳德计算模型计算的应用模型
我能想到的最简单的解释是,单声道是一种用符号化结果组成函数的方式(也称为克莱斯利合成)。“embelished”函数具有签名a->(b,smth),其中a和b是可能彼此不同但不一定不同的类型(想想Int,Bool),smth是“上下文”或“embelisement”。
这种类型的函数也可以写成a->m b,其中m相当于“embelisation”smth。因此,这些是在上下文中返回值的函数(想想记录其操作的函数,其中smth是日志消息;或者执行输入/输出的函数,其结果取决于IO操作的结果)。
monad是一个接口(“typeclass”),它让实现者告诉它如何组合这样的函数。实现者需要为任何想要实现接口的m类型定义一个组合函数(a->mb)->(b->mc)->(a->mc)(这是Kleisli组合)。
所以,如果我们说我们有一个元组类型(Int,String),它表示Int上的计算结果,(_,String)是“embelisation”-动作的日志-和两个函数increment::Int->(Int,String)和twoTimes::Int->(Int、String),我们希望获得一个函数incamentThenDouble::Int->(Int),这是两个函数的组合,也考虑了日志。
在给定的示例中,两个函数的monad实现应用于整数值2增量ThenDouble 2(等于2倍(增量2))将返回(6,“加法1”。中间结果的增量2等于(3,“加1”),2乘以3等于(6,“加3”)
从这个Kleisli合成函数可以导出通常的一元函数。