论坛首页 综合技术论坛

回albertLee:关于Category Theory 和Monad

浏览 24229 次
该帖已经被评为精华帖
作者 正文
   发表时间:2007-12-11  
FP
声明:我没有学过Haskell, 甚至连GHC都是刚下下来还没弄清楚怎么用。对于Haskell一些特性的认识只是这几天通过AlbertLee的翻译稿作了一些很初步的摸索(这里要非常感谢AlbertLee让我节省了不少找资料的时间)。下面文章中涉及Haskell的相关内容很多Haskell的知识只是我从CategoryTheroy角度作出的一些猜测,如果这些地方有错误或者无法说清的地方还是要请学习过Haskell的朋友在贴后加以指正和补充。
说起Monad,似乎无论国内国外的haskell社区里都是谈虎变色的Monster.当然这也难怪, haskell的语法稀奇古怪,我这几天看资料每次看到例子代码都一个头两个大,很多代码都需要做笔算推导你才能知道它干什么(可能是我还不熟悉Haskell的编程环境).当然这只是haskell的壁垒之一,不过我觉得最大的壁垒还不是这个,我觉得最大的壁垒还是在于目的论,也就是说传统程序员不明白为什么Haskell要这样稀奇古怪的处理问题。这个问题稍后再谈,还是先回过头来看看Monad.首先我们来看看设计Monad,主要目的是什么?如果要我来回答的话,我认为是把同一类型的不同操作按照需求逻辑的先后组织起来.举个比较简单的例子, java中的资源清除就可以看成一种类Monad的操作.
public void test()
	{
		ServerSocket sv_sock=null;
		try
		{
			sv_sock = new ServerSocket(9000);
		}	
		catch(IOException e)
		{
			try
			{
			sv_sock.close();
			}
			catch(IOException e1)
			{
				
			}
		}		
	}
这里两个try_block块的工作方式都一样,可以说他们是同一种类型的操作,只是做得事情不一样,按照DRY的原则,我们就应该找到一种办法把这种类型的操作抽象出接口,遵照这个接口对不同的需求实现具体的模块,最后把这些具有同等接口的模块通过复合操作复合起来并且使得最后的复合结果维持同样的使用界面。比如说这个资源清除的例子,如果摆脱java的限制,我们可以这么抽象
handle_error(Fun) throws Exception;
然后分别实现两个handle_error;
handle_socket();handle_close();
我们通过某种复合操作 最后得出一个统一的处理资源关闭的操作,
handle_error resource_clear=handle_socke->add(handle_close); 
如果我们有了这样一个复合操作,那么我们就不必要每次都去写那么冗长的try catch.只要
resource_clear->add(test) 
就可以给test提供统一的资源清理操作。当然这样的操作在java中实现起来比较困难(当然也不是无法解决具体的请参看ajoo在这方面做得一些工作)。
很多语言的教程从这里开始,就要直接用特定语言的优势来说明这个问题在那种语言下的解决方案是如何的elegant.不过我不打算这么做,因为第一我不是在介绍haskell而且我对haskell的了解也非常有限,这些问题最好还是留给lich_ray这样的专家来比较合适,第二我这里主要谈的是这个问题背后的到底是具有什么样的特性?这种特性为何会使得haskell非常容易处理而java非常的难?
为了回答这些问题,我们再回过头去看刚才我们对这个问题进行的描述。昨天和一个朋友谈起这个问题,它说这好像和Design pattern里面的Decorate模式非常相似,我对模式也不太熟悉,粗粗地翻了一下资料,是比较相像不过相对于我们下面要介绍的内容来说Decorate还是一个比较有限的特例。我也不知道能管他叫啥了,胡乱随便起个名字叫做M模式好了。那么我们先从M模式的描述看起,这个模式说,有一系列具有共同特性的东西姑且把他们叫做M1,M2,M3….。有一个操作+,在M1,M2,M3….里面随意选取两个东西比如说M1,M100,然后把+操作运用在他们身上M1+M100其结果为K,使得这个K的特性与M1,M2…都相同,当然其实我们可以把+操作运用在无限个Mi上。
从程序员的角度来说分析到这个地步已经很不错了.不过从数学眼光来看,显然是远远不够的。我想那些还没有把离散数学还给老师的程序员,其实已经在偷偷笑了。这个M模式在群论里就是简单的不能再简单的幺半群monoid.一个monoid(A,.,e),A是一个集合,.是一个associate operator, e是一个单位元.对于任何a,b∈A有,a,b∈A,(a.b).c=c.(b.c),a.e=e.a=a。举个最简单的例子(Z,+,0)Z是整数集,+是普通加法,0是单位元,构成一个幺半群因为任意整数相加总是整数,任意整数加0总是等于它本身。
如果我们使用范畴论的语言来表达就是,我们有一个collection A,带上两个morphism μ:AXA->A,η:E->A。这可能看上去与幺半群的定义不同,实则上是一样的.对于μ来说,实际上对应了associate operator,我们可以把幺半群的associate operator看成一个函数f(a,b)=a.b a,b∈A.那么从范畴的角度来说f就是接收一个pair(a,b)返回一个c ,a,b,c∈A.如果A是一个集合中任意两个元素对构成的集合就可以看成是A的笛卡尔积即AXA。而集合中的笛卡尔积和范畴积没有太大的区别,因此f就可以看成是一个从AXA->A的morphism.再来看η,因为范畴论中规定的每一个对象A必定含有一个叫做identity的morphism即id(A)=A->A,那么由Category product map可以得到ηXid(A)=(E->A)X(A->A)=EXA->AXA,那么
μ o ηXid(A)=(AXA->A)o(EXA->AXA)=EXA->A,也就是a.e=a同理也可得到e.a=a的范畴形式。这告诉我们,在monoid里面,任意个A的积都是A,比如说A3=AXAXA,我们可以通过id(A)Xμ=AXAXA->AXA同上理最后也能得到AXAXA->A。
 这里我们把A看作一个类型的话。那么如果这个类型A,带上了η和μ两个操作,就构成一个monoid。如果从Haskell的角度来说,这种方法实际上和Haskell的Type,Class,Instance系统是一致的。Haskell里面的类型就对应了范畴里面的Collection,Haskell里面的class就对应了范畴里面的morphism,把Type和Class结合到一起那么就构成了一个Instance,也就构成了一个范畴。说到这里我就要叉开一下话题说说刚才所谓的目的论。虽然我对haskell不是太熟悉,但是从我翻资料的结果来看,我相信除了语法壁垒之外,haskell的设计目的可能也是一大学习壁垒。比如haskell中的type,class,instance的概念,虽然作者为了方便理解取了OO中相似概念的名字。然则这两者的概念完全是天差地别,对于传统OO来说class就是type,继承和实现只是对父类的覆盖,instance只是用将类型实体化的一个结果。而haskell里面完全不同,type代表一种数据collection,class代表一种的结构,而instance只是让数据与某种结构后的结果。这种思维和结构主义数学是完全一致的.集合加上二元操作就是一个代数系统,加上单位元和associate operator就成了幺半群,在幺半群上加上逆操作就是群,你可以用这种方法在数据集上加上不同的结构,形成环,域,模。结构主义希望研究不同的结构和结构之间的关系,当一种结构下某一个非平凡问题难以处理时就可以通过映射转换到其他结构上在哪里可能有一套强大的工具可以使得这些问题平凡化。因此,传统的程序员在入门的时候,很难明白这种处理方式的目的是什么?因为传统的语言的目的性非常明确,OO就是让你封装,继承,重用代码。但是haskell这么设计是要干什么?如果没有经过近世代数训练的人,很难明白结构是一种什么东西,我记得上次看到lich_ray讨论Monad实现state的时候也用了别扭两个字来概括,认为Monad在这方面不如continuation来的直观。.因此学习haskell,除非你是像ajoo那样脑瓜很不错的人,否则你就只能下苦功夫靠不断写程序去适应这种思维。
好了继续回到正题。刚才我们讨论了M模式和幺半群之间的关系。但是实际上离我们今天的话题Monad还有非常大的距离。首先我们要处理的collection中的对象是非平凡的,它的元素是各种各样的函数或者称为morphism.第二每一种幺半群中的associate operator都是不同的,比如说整数加法是(Z,+,0)的associate operator,但是它不是(Z,.,1)的associate operator。那么这个由一系列morphism构成的collection的associate operator是什么呢?这是我们下面需要讨论的核心问题.
要解决这些问题,我们手边的工具幺半群和集合函数显然是不够用了。接下来我们就要深入到范畴论中去寻找答案。我们刚才说了,type就好象规定了一类collection,比如说int型实则是规定了一个对象为正整数的collection.而class则规定了一个范畴中的morphism集,让一个type instance了某个class就构成了一个范畴。好我们第一步就是要拓展范畴形成算子Functor。什么是算子呢?很简单,我们就是要把范畴之间的映射作为研究对象。我们有两个范畴C,和D,有一对函数(Fo,Fm),Fo:c->d c∈C,d∈D,f:a->b,g:b->c,a,b,c∈C,那么有Fm(f)=Fo(a)->Fo(b),Fm(gof)=Fm(g)oFm(f). (Fo,Fm)称为C到D的算子F:C->D

我们只要画一个交换图就很好理解了。接下来我们看一些haskell中的小例子,在haskell中每一个type的类型构造器就是算子的一部分。比如最简单的一个类型,
data Salary= Dollars a | Nothing.
它表示Salary是一个工资,a代表具体的工资额度,Nothing代表没有工资。在haskell中Salary除了是一个类型以外,其实还是一个构造函数,Salary:: a->Salary a.它的意思是接受一个a,返回一个Salary a.如果我们把正整数理解为一个范畴,把Salary类型理解为另一个范畴,那么Salary构造器实际上就是一个算子的一部分也就是上面定义中Fo,它负责把正整数范畴中的对象,映射成Salary范畴中的对象。根据定义算子还差了一个映射Fm负责把正整数中的morphism映射为Salary的morphism.比如说我们有一个需求,要把工资加一倍。那么我们就要对这个Salary 实现一个叫做Functor的class,
class Functor f where
   fmap: (a->b)->(fa->fb)
这个class告诉我们,函数fmap接受一个函数k:a->b,a,b∈C从对象a映射到对象b,一个Fo算子f: c-> newc,从范畴C映射到新范畴NEWC.其返回结果是一个范畴NEWC中的函数
f(a)->f(b). f(a),f(b) ∈NEWC。好一旦Salary instance了这个class那么就形成了一个算子范畴。
instance Functor Salary where
    fmap f  Nothing    =  Nothing
fmap f  (Dollars x)   =  Dollars (f x)
那么我们现在定义一个函数 double x = x*2.然后运行
fmap double (Dollars 100)
.这里fmap double Dollars,做的是生成一个新函数,slaray_double=Slarary(f),然后对slaray_double (100)进行求值, 返回Dollars 200。事实上Functor是比较简单的。下面介绍以后我们要用的两个Functor.一个称为Identity Functor Id:C->C, ido:a->a,idm(f):a->b.这个Functor很简单就是把一个范畴完全的映射回自己,另一个Fucntor称为endofunctor,T:C->C,to:a->b,tm(f):to(a)->to(b).这里T是一个C到C的Functor,不过它和Identity Functor不同,它不一定把一个对象映射回其自身。比如说一个范畴的对象是整数,那么T可以把负整数映射为0,正整数映射为1.
这里我再扯一点题外话,Functor 这个class应该说体现了Haskell中一个重要的DRY技巧。无论是在OO语言,还是其他的函数式语言里。复用一个函数的代码,往往是采用把代码塌缩成一个可以传递的值,OO中把代码塌缩为一个接口,函数式语言里塌缩为一个函数变量。第二步对原来的代码打洞比如OO中的Ioc就是一种打洞技术,然后通过值传递的方式把这段代码注入到原来的代码里。但是Haskell这里的思想方法是完全不同的,Haskell是一种映射的概念.如果一个函数f是在某一个范畴空间中的morphism,那么你要对这个范畴做一个扩展时,首先把范畴中的对象映射到新范畴,然后通过算子把原来范畴空间中的函数映射到新的范畴空间。比如我现在有一个从整数范畴中映射出来的年龄对象Age,那么通过算子可以把double原封不动的映射到Age上。
  现在我们现在实现了算子,但是离Monad还有一步。接下来我们需要构造所谓的算子的算子,这有什么用呢?假设我们从正整数范畴里通过算子映射出两个算子范畴,如果这两个算子范畴想要互相使用对方的代码,该怎么办呢?这里我们就可以像算子把范畴当作映射对象一样,我们可以把算子也当成映射对象。这种算子之间的范畴,称为自然变换natural transformation. natural transformation的定义是这样的,给定两个范畴C,D,有两个算子F,G:C->D都是C到D的算子,那么τ:F->G就是一个从F到G的自然转换,对∨a∈C,有
τ(a):F(a)->G(a),对∨f:a->b,a,b∈C 有τ(b)oF(f)=G(f)o τ(a).同样我们也用一个交换图来表示这个自然转换:

上面的虚线部分就是自然转换映射。我们还是来延续上面工资的小例子,我们现在有两种工资:
data DollarsSalary a= DollarsSalary a | None deriving Show
data YaunSalary a= YaunSalary a | None1 deriving Show
让他们分别实现 Functor
instance Functor DollarsSalary where
    fmap f  None    =  None
    fmap f  (DollarsSalary x)   =  DollarsSalary (f x)
instance Functor YuanSalary where
    fmap f  None1    =  None1
    fmap f  (YuanSalary x)   =  YuanSalary (f x)  
 
然后我们实现一个,从Dollar到Yaun的汇率函数:
rate (DollarsSalary x)= YuanSalary (x*7)
然后再与double函数复合成natural transformation:
 rate $ fmap double $ (DollarsSalary 100)
我们令算子 D为DollarsSalary算子,Y为YuanSalary算子。那么下面这副交换图就形成了一个natural transformation

首先看纵向的两个Morphism就是natural transformation
η:D->Y
rate :: DollarsSalary a -> YuanSalary a
左边ηa:D(a)->Y(a),
DollarsSalary 100 ===> YuanSalary 700
右边ηb:D(b)->Y(b),
DollarsSalary 200 ===> YuanSalary 1400
横向的两个Morphism就是两个算子
double::a->b
上边D 算子: D(double)::D(a)->D(b)
 
fmap double:: DollarsSalary a -> DollarsSalary b

  DollarsSalary 100===> DollarsSalary 200
下面的Y算子:Y(double):Y(a)->Y(b)
 
fmap double:: YuanSalary a -> YuanSalary b

  YuanSalary 700===> YuanSalary 1400
左下与上右的morphisms可交换(communitive)
ηb o D(double)= Y(double) oηa
好了到此为止,我们今天所要用到的范畴论技术已经差不多了。先让回到我们的主题M模式, 我们前面在描述M模式的时候这样说过:“有一系列具有共同特性的东西M1,M2,M3….”这只是一个非常模糊的语言描述,怎么样才能说具有共同性呢?有相同类型?有相同的值?有相同的名称?有相同的行为?无论怎么罗列恐怕都是以偏概全,挂“一”漏“万”。那么我们怎么定义这个共通属性呢?从范畴论的思维来说,联系是普遍的,变是不变的.这是什么意思呢?它的意思是说,对象与对象之间不是孤立而是普遍联系,我们所研究的对象总是千变万化的,但是连接对象之间的关系是永恒不变的。举一个简单的例子,我们在集合论中说,集合C是B的子集采用的是一种描述性的语句,C={a|a∈B}。而从范畴论的角度来看这样的描述未免太狭隘了。在范畴论看来,任何f:C->B只要f构成monomorphism,那么就可以说f确定了一个子集。所谓的monomorphism,从集合论来说就是类似injective 单射(不过并不完全一致),对于任何x,y∈A,若f(x)=f(y)那么必有x=y.看下面这张图,就很容易理解了,f(x)只要能单射到B,那么C必定与B的子集Imf={f(x)|x∈C}同构。换一句话说在范畴论的语言中,具体某一个子集是无关紧要的,重要是能否找到两个集合之间的monomorphism。。

换而言之,在范畴论看来子集具体是怎么样的并不重要,重要的是找到能够确定子集关系的映射。

顺带再扯一些题外话,如果我们前面在讨论type,class,instance的时候还能看到所谓的结构的话,那么在我们在这里已经全然看不到所谓的结构踪影。在范畴论看来,许多数学研究领域都可以归结成一些恰当的范畴,例如所有集合的范畴,所有群的范畴,所有拓扑的范畴,等等。这些范畴里有一些“特殊的”对象,例如空集或者两个拓扑的直积。然而,在范畴的定义里,对象是原子性的,我们不去关心一个对象到底是集合,是拓扑,还是其它抽象概念.要定义这些对象的性质而不涉及对象的内在结构,是一个难度极大的挑战.解决这个问题的途径是借用对象和对象之间的关系,而这些关系由相应范畴中的态射给出.因此结构性的问题可以转化为寻找泛性质,通过讨论这些泛性质我们就可以唯一地决定我们所感兴趣的对象.

  那么我们再回过头来看,什么是具有共同性呢?我们可以说,具有什么样的共同性都是无关紧要的,而由映射到某个特定范畴的算子所决定的泛性质才是重要的。也就是说算子f:a-> m a抽象了对象之间的共同性,换一句话说f赋予了某类对象a映射到范畴m的能力。只要有这样的能力,那么a就能复用范畴m中的所有morphsim.由此可以看出,我们所讨论的M模式其核心处理对象是算子。回想一下M模式和monoid的关系,我们现在把monoid推广到算子上,把natural transformation看成是算子的复合,那么M模式实际上就是一个算子经过N次natural transformation转换后都与自己相同,这样的natural transformation就是所谓的Monad。我们在这里首先给出Monad的形式定义:
令C是一个范畴,那么C上的Monad就是一个三元组(T,μ,η), T:C->C是一个endofunctor.
μ和η是两个natural transformation,μ:ToT->T η:Idc->T,其中Idc是C上的identity functor,ToT是两个算子的复合对于∨x∈C,ToT=T(T(x))
如果你稍微看过Haskell的Monad 的一些介绍,应该看出上面的定义其实与Monad class很像了,
class Monad m where
  return :: a-> m a
  (>>=) :: m a-> (a-> m b) -> m b
其中η就是return函数。因为Idc是C上的indentiy functor, ∨x∈C Idc(x)=x.T:C->C是一个endofunctor,这里的范畴C可以理解为Haskell中所有可能的类型,因此我们可以把类似DollarsSalary:: a-> DollarsSalary a这样的算子看成是把任意一种类型a映射成DollarsSalary DollarsSalary 本身还在haskell的所有类型范畴中,这也是与Monad Class定义是一致的a是polymorphic的也就是类似C++的泛型。η是一个自然转换,那么∨a∈C,都有ηa:a-> m a,也就是return函数了。
不过μ:ToT->T 就显得奇怪了。如果按照natural transformation的定义,那么∨x∈C,
μx=T(T(x))->T(x)。这就和monad class的bind操作有很大差别了.不过实际上他们的差别并不很大。我们先来看bind操作干了一些什么,他是一个函数接受两个参数,一个m a,另外一个是函数 g:a->m b,然后返回一个m b.Monad操作就像是一部电梯,return函数就好像是电梯在底楼装了一个人a进电梯间然后关门变成m a,然后这个电梯运行到楼层g,a 出电梯间进入g楼,然后换另外一个乘客 b 进入电梯间,关门变成m b. 你可以用>>=链接任意多的楼层,这部电梯每经过一个楼层把乘客换下,搭载新的乘客到下一楼。我们可以看到其实>>=抽取了一个由m a->a的逆算子,这是为何呢?因为虽然像DollarsSalary这样的类型,把需要运算的数据剥离出来非常的方便,但是还有许多Monad的类型比较复杂比如state,cps等等。因此在>>=中抽取逆算子还是非常有意义的,它能让被绑定函数f专注于对数据的操作而不用关心外层封装。刚才我们说,是否具有共同性,取决于是否能够找到算子f:a-> m a。.现在函数g,要能够进入范畴Monad参与运算,那么首先就要通过算子f:a->m a将其映射到Monad范畴里(请注意这里g:看上去和算子f:a->m a有些类似很多情况下会导致混淆,请注意算子的定义,g:a->m b不是算子)。根据算子的定义f(g):m a->m m b.现在bind函数就和Monad的形式定义衔接上了。对于C中的morphism,f:a->T(b),经过算子T有T(f):T(a)->T(T(b)),那么μb o T(f)=T(T(b))->T(b) o T(a)- >T(T(b))=T(a)->T(b).最终和bind >>=的定义同构。在haskell中范畴论的定义实际上是join函数.根据Monad first law, m>>=f= join fmap f m,

我们再回过头去看看我们最先提到的try_catch的例子。在Haskell有一类monad叫做error monad他的作用就是在于把不同层次级别的错误处理代码进行独立实现,然后依靠bind函数依据需求把不同的错误处理代码串联成一个完整的错误处理器。其具体的实现,有兴趣的朋友可以自行搜索相关资料,这并不是这里的讨论重点。我这里只是用error monad来实现一下资源清理的工作
data Exception = Err {socket::Socket, reason::String}
instance Error Exception where
  noMsg    = Err 0 "Socket is corrupted"
  strMsg s = Err 0 s
printString s = return $ show s
socketException e=return $close(socket(e))
closeException e= return $“socket close error because of”++(reason e)
let (Right str) = do {n <- SocketRead s; printString n} `catch` socketException `catch` closeException
            in str
我想把这个实现作为一个讨论的话题留给大家.我想提出的问题是,在Java中我们如何去实现M模式?与Haskell比较起来,他们的差异的在什么地方?设计模式中的Decorate模式是不是也是一种M模式?Decorate模式的具有什么样的限制?在大家看过本文以后可以继续深入地讨论这些问题。也希望提出批评意见特别是有关haskell部分的代码大都是摘抄样例程序稍作修改而来,因为不太会用GHC所以也没有测试过是不是正确。Haskell的很多概念,也只是从All about monad和YAHT里面翻译出来的,理解不一定正确。
  • 大小: 9.7 KB
  • 大小: 15.4 KB
  • 大小: 6.3 KB
   发表时间:2007-12-11  
imag  
  • 大小: 8.9 KB
0 请登录后投票
   发表时间:2007-12-11  
好好学习偶像的大作
0 请登录后投票
   发表时间:2007-12-12  
太感谢了,今天T1老大鞭策我不能惰遁,呵呵~~

今天谈的时候,说到我对 monad 的理解,我认为是对计算过程的一种抽象,可以看成是计算过程的设计模式。

楼主举的java清理服务器的例子很贴切。 haskell 中的 monad,比如 Maybe ,就是把

result1 = F()
if result1 != Nothing:
    result2 = G(result1)
    if result2 != Nothing
        ....
    else:
        return Nothing
else:
     return Nothing

这一类的操作,变成:
F() >>= G()
这样。

只要一个函数返回 Maybe 类型,就可以串起来。


0 请登录后投票
   发表时间:2007-12-12  
昨天AlberLee说对Monad law似乎搞不太清楚.因为初学haskell的传统程序员的数学背景都不很强,所以一般他们都是从语言层面上入手,基本上只能依靠类型推导来分析Monad的执行过程.但是这个过程非常不直观,一来haskell没有很好的debug工具,第二haskell的类型系统违反直觉和经验.这种不直观的特性,传统程序员主最后主要依靠的手段就是去找一个相似的对应物进行辅助理解。比如说学习OO的时候,我们总是会举出圆正方形长方形,或者是汽车卡车火车之类的直观形象的例子辅助理解。但是在学习Haskell特别是Monad的时候,就难以找到这样的例子,你只能一头栽进类型推导的演算里去分析其中的规律。而Monad Law又进一步把问题复杂化,return和bind的几种复合操作绝对会让90%的传统程序员迷失在类型推演的迷宫里。
因此,我倾向于从数学概念上去理解Monad,这会让monad law看上去十分的自然。我在上面说过,Monad其实是Monoid在算子上的推广.那么Monoid有什么特性呢?一个整数加法的幺半群(Z,+,0).有下面几个规则也就是law,
law1: a+b=c a,b,c∈Z
law2: a+0=a a∈Z
law3: 0+a=a a∈Z
law4  (a+b)+c=a+(b+c)
那么同理,Monad里面,我们也可以把它看成幺半群。自然转换η,也就是Monad class里的return函数实则就是单位元类似于0。自然转换μ也就是(>>=)bind函数就是associate operator,类似于+号。那么套用
Monoid的规则,就很容易理解Monad的三个law.
1. (return x) >>= f == f x
  这里说把return当作左单位元,经过一个(>>=)函数与某一个f复合,最后还是得到f
2. m >>= return == m
    这里说把return当作右单位元,经过一个(>>=)函数与某一个输入值m a复合,最后还是得到m a
3. (m >>= f) >>= g == m >>= (\x -> f x >>= g)
  这里说的是结合率,当一个输入值m a通过(>>=)bind函数先与f复合,然后再与g复合。等于先让f与g通过
(>>=)复合再与输入值m a复合.
0 请登录后投票
   发表时间:2007-12-12  
我觉得T1的这篇文章还是有意义的。不是直接告诉别人去查某某课本,而是把知识以自己整理后的形式发布。

不过学而不思则罔,那么我想问以下几个问题:

1. Monad有什么好处?
2. 由Monad概念出发所构造的其他具体的东西有什么好处?

T1问了一个有趣的问题,在Java中如何实现M模式?有人可能会把这句话理解为如何在java中实现Monad. 或者说如何以一种非常普遍的方式在java中实现类似Monad的机制。显然,在函数式语言之外是没有直接的Monad的对应物的。因此从我的视角出发,我会问如下的问题:

3. 在函数式语言之外,Monad的这种有用性,即不是Monad而是它带给我们的价值是什么, 如何体现?是否有某种结构形式带给我们与Monad同样的便利,而在形式和思想内涵上它们和Monad也有着那么一点类似?
这就如同, 有人告诉我金子很有用, 但是我发现我对金子的需求其实是要它作为硬通货来使用, 因此我发行了一些看似无价值的纸币, 但是它们一样带给我们同样的价值.
0 请登录后投票
   发表时间:2007-12-12  
确实不是读一两遍能读懂的,不懂近世代数的传统程序员。

尝试说下我对 canonical的三个问题的理解:

1。 Monad 建立了一种抽象机制。
2。 具体的东西,举个例子: parsec monad,通过combinator构造解析器,写起来非常直观易懂,代码少。直接的好处就是程序可维护。
parsec 的例子可以看 Write your Scheme in 48 hours

我不懂 Java和它的市场策略。

3。 对于纸币与价值的问题,我不懂经济学。
0 请登录后投票
   发表时间:2007-12-12  
其实我的问题是除了那些别人已经明确写下的,与函数式明显相关的例子,有没有人能够独立的想象到monad的形象,并在非函数式语言中找到类似的应用效果。parsec显然还是函数式内部的例子。这当然不用思考,因为haskell的核心部分就包含了monad.

关于数学学习的一个问题就在于,很多人只能在原始的语境下通过书本已经明确阐明的思维线索来做表面的理解。这样并得不到真正的知识,因为你并没有具有把它应用到其他未阐明的情形下的能力。
0 请登录后投票
   发表时间:2007-12-13  
写得太好了,好难理解的monad,看得我快疯了
0 请登录后投票
   发表时间:2007-12-15  
Decorator模式中所有的Decorator实现了所要修饰的对象所从属的功能接口,可以把所有的具体的Decorator看为M模式的元素M1,M2...,
M1+M2+M3...是否仍为M的元素,让+操作也成为一个Decorator即可。
如new LinkDecorator(new IODecorator(newIOFunctor1()),nextFunctor)
LinkDecorator为+操作
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics