学 C 语言,拦路虎是指针。学 Haskell,拦路虎是单子(Monad)。学 Scheme,拦路虎是什么呢?初学者以为是层峦叠嶂的括号,其实是续延(Continuation)。segmentfault
我曾用过 C 的指针(数据指针 + 函数指针)模拟过单子 [1],其实已经触及了续延。还记得那个 bar
函数吧,函数
Maybe bar(Maybe a, Maybe (*contuation)(void *thing)) { return a.thing ? contuation(a.thing) : (Maybe){.thing = Nothing}; }
contuation
指针指向一个函数。这个函数用于处理 bar
不可以肯定或不知道如何处理的事情,这种事情对于 bar
而言,可称为一切来自将来的计算,此时,这个函数能够称为 bar
的续延。也就是说,bar
函数完成它能完成的计算以后,会将计算结果传递给 contuation
。固然,这在 C 语言里是无法作,可是能够这样认为。指针
要作到「这样认为」,须要作一点脑力体操。code
看下面的表达式,它基于 bar
函数输出结果的赋值表达式,可是我故意不给 bar
函数提供第 2 个参数,而是用一个 []
来表示它:内存
Maybe b = bar(foo(Nothing), []);
假若我说,这个表达式等效于 1 个单参数函数,你信么?get
假若你不信……我想不出来为啥你会不信。须要 1 个参数才能求值的计算过程,不就是一个单参数的函数么?编译器
不信的话,那么就将 bar(foo(Nothing), [])
打开,即:io
return foo(Nothing).thing ? [](foo(Nothing).thing) : (Maybe){.thing = Nothing};
看上去,是一些表达式包围着 []
。从如今起,要认为 []
表示一个洞,洞外的表达式掉入了这个洞,然后洞里发生了一些事,最后从这个洞里又掉出来一个东西。要达到这种认识,可能须要懂一点王阳明创立的心学,即「心外无物,心外无理」。你以为是这些表达式包围了洞,可是没了这个洞,这些表达式是没意义的,等同于不存在。编译
假若仍是没开窍,那就拿一只杯子。将杯子的内部视为「外」,将杯子的外部视为「内」,那么这个杯子就包含了整个宇宙。假若这个杯子不存在,那么宇宙也就没了意义,等同于不存在。接下来,你拿锤子敲掉这个杯子的底,你能够说这个宇宙从杯子的一个口掉了进去,又从另外一个口掉了出来。test
袖里乾坤大,壶中日月长。不吹,也不黑。
通过上述脑力体操以后,我宣称,bar
函数的返回值掉进了这个洞里,而后从这个洞里出来的东西做为值赋给了 b
,应该很好理解。尽管它与咱们的生活经验不太相符。
记住,带洞的表达式就是续延。能够认为,这个洞外面的东西会掉进这个洞里。不妨将这个洞称为「黑洞」。它对掉进来的东西做何处理,要看这个黑洞是什么样的黑洞了。
对于
bar(foo(Nothing), []);
下面这个函数是一个对掉进来的整型数乘以 10 的黑洞:
Maybe test(void *thing) { (*(int *)thing) *= 10; return (Maybe){.thing = thing}; }
从这个黑洞里掉出来的值,最终被赋给 b
,即:
Maybe b = bar(foo(Nothing), test);
再强调一下,在 C 语言里不是这样进行运算的,只是能够这样认为,并且只要你愿意,能够为 C 语言写一个这样的编译器。
如今,将 b
的赋值表达式的右半部分去掉,让它变成:
Maybe b = [];
这样,一个赋值表达式也能构成一个续延,由于它里面出现了黑洞。掉进黑洞里的 b
(内存地址),会被黑洞与一个值(数据)绑定起来,而后它们从这个黑洞里掉出来。
基于续延里的黑洞,可以将多个续延串接起来,只须要将黑洞串接起来便可。例如,在 C 语言里,我要用 bar
组合一组相同类型的函数,能够这样作:
Maybe x = bar(bar(bar(foo(a), f), g), h);
注: 假设a
已知,foo(a)
构造一个Maybe
变量。
这个表达式有三重续延,它们像俄罗斯套娃同样。在 C 语言里,只能经过「看做是」的办法,认为整个表达式首先掉进了最内层的黑洞 f
。从 f
里掉出来东西又掉进了第二层黑洞 g
。从 g
里掉出来的东西又调入了第三层也就是最外层黑洞 h
。从h
里掉出来的东西做为值赋给变量 x
。
将一个续延做为参数传给另外一个续延,这就是所谓的续延传递,用这种办法能够造成一条有序的控制流(几个黑洞衔接起来)。用这种办法写程序,就是所谓的续延传递风格(Continuation-Passing Style,CPS)。
以上所述的东西,对于 C 的世界而言,是想象,可是对于 Scheme 的世界而言,就是客观存在。在 Scheme 的世界里,函数与续延,都是一等公民。函数能够做为参数传递给函数,续延能够做为参数传递给续延,函数能够做为参数传递给续延,续延能够做为参数传递给函数。
在 Scheme 里,假设也有一个 bar
函数,那么它将 f
、g
、h
这三个黑洞串接起来并将最后一个黑洞掉出来的结果赋值给一个变量,可用如下手法:
(define x (bar (foo a) (lambda (b) (bar (f b) (lambda (c) (bar (g c) (lambda (d) (foo d))))))))
值得注意的是,bar
函数是单子的核心部分,而 bar
函数能够视为一个续延,它的黑洞就是 contuation
指向的函数。如此说来,续延要比单子更基层。不过,单子也不是盖的,它也有办法规避 bar
这样的函数,并且使得本身依然是单子,而且它还能演绎出 bar
这样的函数。那么,将续延和单子当作一回事,如何?
在实践中,续延一般用来表达「等待」。
有诗云,有约不来过夜半,闲敲棋子落灯花。敲着棋子看灯花的人是一个续延,他掉进了爽约的人构成的黑洞里,从而失去了时间。
最后,来作一道哲学题:
先生(指王阳明)游南镇,一友指岩中花树问曰:「天下无意外之物,如此花树在深山中自开自落,于我心亦何相关?」先生曰:「你未看此花时,此花与汝心同归于寂;你来看此花时,此花颜色一时明白起来,便知此花不在你的心外。」王阳明这一观点的错误是( )
A.把人对花的感受与花的存在等同起来
B.把人对花的感受与花的存在对立起来
C.主张人对花的感受是主观与客观的统一
D.确定人对花的感受的能动性
这个题目,上过大学,学过马哲的人差很少都作过。王阳明龙场悟道,创立心学。他的成就,即便放到现代,能超越的人几乎不存在。结果,堂堂一代宗师,就被这么一个题目给毁了。
王阳明的观点没有错误,反正马克思没说他错了。说他这个观点错了的人,是学过马哲但没学过心学的人。大学不教心学,只教马哲,因此呢,全中国的大学生都会以为王阳明太惟心,并且仍是主观惟心……惟心就是封建迷信……至少我上学的时候,作了这个题目以后就是这么认为的。
观点自己没有对错。只是观点与受体可能会存在类型不匹配问题,从而触发了受体的异常机制。
有点跑题了。我要说的是续延。对于王阳明看花这个例子,能够理解为,花是一个续延,王阳明是它的黑洞,从这个黑洞里掉出来的结果是「王阳明心中的花」。王阳明的友人也是这朵花的黑洞,从这个黑洞里掉出来的结果是「王阳明友人心中的花」。那朵花自己没有变,只是掉入了不一样的黑洞里时,输出的结果有所不一样。
假若全世界的人或者任何虫鱼禽兽都没看过那朵花,它会怎样?我不是很清楚。不过,假若我是那朵花,我会尝试本身「看」本身——将本身做为续延,再将这个续延做为黑洞,结果获得的是「自我」。
[1] 单子,想弄不懂都很难