大街复合函数求导公式大全数

&ul&&li&&b&扩展函数&/b&&/li&&/ul&&p&Kotlin大量的语法糖都依靠扩展来实现,代码简洁方面要比Java写个Util类要强。&/p&&p&举个例子,String有split操作然而却没有join操作,想要join操作常见的做法是搞个Util类 (比如android.text.TextUtils),如果用Kotlin,可以对Iterable扩展一个joinToString方法:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&n&&val&/span& &span class=&n&&parts&/span& &span class=&o&&=&/span& &span class=&s&&&http://url/.html&&/span&&span class=&o&&.&/span&&span class=&na&&split&/span&&span class=&o&&(&/span&&span class=&s&&&-&&/span&&span class=&o&&)&/span&
&span class=&n&&parts&/span&&span class=&o&&.&/span&&span class=&na&&joinToString&/span&&span class=&o&&(&/span&&span class=&n&&separator&/span& &span class=&o&&=&/span& &span class=&s&&&-&&/span&&span class=&o&&).&/span&&span class=&na&&let&/span&&span class=&o&&(::&/span&&span class=&n&&println&/span&&span class=&o&&)&/span& &span class=&c1&&// http://url/.html&/span&
&/code&&/pre&&/div&&p&比起TextUtil.join(&-&, parts)要优雅一些些,joinToString在标准库里有实现:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-f718d77d8b538eca175fa6ec_b.png& data-rawwidth=&865& data-rawheight=&580& class=&origin_image zh-lightbox-thumb& width=&865& data-original=&https://pic1.zhimg.com/v2-f718d77d8b538eca175fa6ec_r.png&&&/figure&&p&(?ω?)ノ 可以看到扩展函数是可以带泛型参数的,除此之外还能针对Nullable的类型扩展&/p&&p&比如Anko就是这么给View的所有子类加扩展的:&/p&&figure&&img src=&https://pic4.zhimg.com/v2-29c369feafbfd0e07ca1e6c76c34bcdf_b.png& data-rawwidth=&514& data-rawheight=&168& class=&origin_image zh-lightbox-thumb& width=&514& data-original=&https://pic4.zhimg.com/v2-29c369feafbfd0e07ca1e6c76c34bcdf_r.png&&&/figure&&br&&p&再比如RxJava1.x里,类似这样的操作很常见:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&n&&Subscription&/span& &span class=&n&&subscription&/span& &span class=&o&&=&/span& &span class=&n&&Observable&/span&&span class=&o&&.&/span&&span class=&na&&create&/span&&span class=&o&&(...)&/span& &span class=&c1&&// 以下链式调用部分可能有上百行&/span&
&span class=&o&&.&/span&&span class=&na&&map&/span&&span class=&o&&(...)&/span&
&span class=&o&&.&/span&&span class=&na&&subscribeOn&/span&&span class=&o&&(...)&/span& &span class=&c1&&// (1)&/span&
&span class=&o&&.&/span&&span class=&na&&observeOn&/span&&span class=&o&&(...)&/span& &span class=&c1&&// (2)&/span&
&span class=&o&&.&/span&&span class=&na&&subscribe&/span&&span class=&o&&(...);&/span&
&span class=&n&&compositeSubscription&/span&&span class=&o&&.&/span&&span class=&na&&add&/span&&span class=&o&&(&/span&&span class=&n&&subscription&/span&&span class=&o&&);&/span&
&/code&&/pre&&/div&&p&subscription往往要批量做取消订阅之类的处理(这里用了CompositeSubscription来管理),另外线程调度的操作也许会用得比较多,也许你很聪明的能想到把(1)和(2)用compose处理一下:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&n&&Subscription&/span& &span class=&n&&subscription&/span& &span class=&o&&=&/span& &span class=&n&&Observable&/span&&span class=&o&&.&/span&&span class=&na&&create&/span&&span class=&o&&(...)&/span&
&span class=&o&&.&/span&&span class=&na&&map&/span&&span class=&o&&(...)&/span&
&span class=&o&&.&/span&&span class=&na&&compose&/span&&span class=&o&&(&/span&&span class=&n&&RxUtil&/span&&span class=&o&&.&&/span&&span class=&n&&xxx&/span&&span class=&o&&&&/span&&span class=&n&&async&/span&&span class=&o&&())&/span&
&span class=&o&&.&/span&&span class=&na&&subscribe&/span&&span class=&o&&(...);&/span&
&span class=&n&&compositeSubscription&/span&&span class=&o&&.&/span&&span class=&na&&add&/span&&span class=&o&&(&/span&&span class=&n&&subscription&/span&&span class=&o&&);&/span&
&/code&&/pre&&/div&&p&不过如果是用Kotlin,通过给Observe&T&加async,给Subscription加addTo扩展函数,你可以做得优雅一些:&/p&&div class=&highlight&&&pre&&code class=&language-kotlin&&&span class=&n&&Observable&/span&&span class=&p&&.&/span&&span class=&n&&create&/span&&span class=&p&&(...)&/span&
&span class=&p&&.&/span&&span class=&n&&map&/span&&span class=&p&&(...)&/span&
&span class=&p&&.&/span&&span class=&n&&async&/span&&span class=&p&&()&/span&
&span class=&p&&.&/span&&span class=&n&&subscribe&/span&&span class=&p&&(...)&/span&
&span class=&p&&.&/span&&span class=&n&&addTo&/span&&span class=&p&&(&/span&&span class=&n&&compositeSubscription&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&(?ω?)ノ 另外Kotlin的lambda也会让使用了RxJava的代码可读性更强&/p&&blockquote&注意扩展函数的优先级比较低,会被类本身已有相同方法覆盖掉,不过IDE会有很贴心的提示,嗯..&/blockquote&&br&&ul&&li&&b&lambda&/b&&/li&&/ul&&p&以下代码(略有修改)摘自&a href=&//link.zhihu.com/?target=https%3A//github.com/johnlindquist/AceJump& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&johnlindquist/AceJump&/a&,实现根据不同的keyCode触发相应操作的功能:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-da5a38ce375cd94ed6a3c19ee370614e_b.png& data-rawwidth=&900& data-rawheight=&372& class=&origin_image zh-lightbox-thumb& width=&900& data-original=&https://pic3.zhimg.com/v2-da5a38ce375cd94ed6a3c19ee370614e_r.png&&&/figure&&p&Map中的键是keyCode,值是一个lambda。如果要用Java实现,估计要定义一个接口,会麻烦一丢丢,没错..还能把策略模式、访问者模式用上 (?ω?)ノ&/p&&blockquote&用typealias重命名lambda也许能增加可读性&/blockquote&&br&&ul&&li&&b&inline扩展函数+后置lambda&/b&&/li&&/ul&&p&大部分&b&成对的操作&/b&都可以用inline扩展函数+后置lambda让代码变得更优雅,比如标准库里的这个lock写法,就隐藏了try-finally的模板:&/p&&div class=&highlight&&&pre&&code class=&language-kotlin&&&span class=&k&&val&/span& &span class=&py&&lock&/span& &span class=&p&&=&/span& &span class=&n&&ReentrantLock&/span&&span class=&p&&();&/span&
&span class=&k&&val&/span& &span class=&py&&result&/span& &span class=&p&&=&/span& &span class=&n&&lock&/span&&span class=&p&&.&/span&&span class=&n&&withLock&/span& &span class=&p&&{&/span&
&span class=&c1&&// access a locked resource&/span&
&span class=&p&&}&/span&
&span class=&k&&val&/span& &span class=&py&&readWriteLock&/span& &span class=&p&&=&/span& &span class=&n&&ReentrantReadWriteLock&/span&&span class=&p&&()&/span&
&span class=&n&&readWriteLock&/span&&span class=&p&&.&/span&&span class=&n&&read&/span& &span class=&p&&{&/span&
&span class=&c1&&// execute an action with a read lock&/span&
&span class=&p&&}&/span&
&span class=&n&&readWriteLock&/span&&span class=&p&&.&/span&&span class=&n&&write&/span& &span class=&p&&{&/span&
&span class=&c1&&// execute an action with a write lock&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&(?ω?)ノ 聪明的你应该能联想到:io流、数据库事务之类的操作也能这样子写&/p&&br&&p&inline扩展函数+后置lambda常见的应用还有标准库里的:run、with、apply、also、let等函数,建议灵活使用,有趣的是他们其中有的可以改变上下文(this),比如创建实例并初始化的操作:&/p&&div class=&highlight&&&pre&&code class=&language-kotlin&&&span class=&c1&&// Paint&/span&
&span class=&n&&mPaint&/span& &span class=&p&&=&/span& &span class=&n&&Paint&/span&&span class=&p&&().&/span&&span class=&n&&apply&/span& &span class=&p&&{&/span&
&span class=&n&&color&/span& &span class=&p&&=&/span& &span class=&n&&PAINT_COLOR&/span&
&span class=&n&&textSize&/span& &span class=&p&&=&/span& &span class=&n&&textSize&/span&
&span class=&n&&textAlign&/span& &span class=&p&&=&/span& &span class=&n&&Paint&/span&&span class=&p&&.&/span&&span class=&n&&Align&/span&&span class=&p&&.&/span&&span class=&n&&CENTER&/span&
&span class=&n&&strokeWidth&/span& &span class=&p&&=&/span& &span class=&n&&STROKE_WIDTH&/span&
&span class=&n&&strokeCap&/span& &span class=&p&&=&/span& &span class=&n&&Paint&/span&&span class=&p&&.&/span&&span class=&n&&Cap&/span&&span class=&p&&.&/span&&span class=&n&&ROUND&/span&
&span class=&p&&}&/span&
&span class=&c1&&// WebView&/span&
&span class=&n&&mWebView&/span&&span class=&p&&.&/span&&span class=&n&&settings&/span&&span class=&p&&.&/span&&span class=&n&&run&/span& &span class=&p&&{&/span&
&span class=&n&&javaScriptEnabled&/span& &span class=&p&&=&/span& &span class=&k&&true&/span& &span class=&c1&&// NOTICE: XSS&/span&
&span class=&n&&builtInZoomControls&/span& &span class=&p&&=&/span& &span class=&k&&false&/span&
&span class=&n&&displayZoomControls&/span& &span class=&p&&=&/span& &span class=&k&&false&/span&
&span class=&c1&&// other setting...&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&(?ω?)ノ 事实上有了apply,Builder模式已经变得可有可无了&/p&&br&&p&很多刚接触Kotlin的Android开发者常写这样的代码:&/p&&div class=&highlight&&&pre&&code class=&language-text&&var userInfo: UserInfo? = null
tvName.text = userInfo?.userName
tvPhone.text = userInfo?.phone
// show data from userInfo...
&/code&&/pre&&/div&&p&毕竟Android框架到处都是回调,很多成员变量都是在生命周期的回调中赋值的,但是Kotlin又需要控制Nullable。这时候可以考虑用lateinit关键字(当然你要记得初始化),或者另外一种写法:&/p&&div class=&highlight&&&pre&&code class=&language-text&&userInfo?.run {
tvName.text = userName
tvPhone.text = phone
&/code&&/pre&&/div&&p&这时候在block内,userInfo一直是非null的,run还顺手把上下文打开了 (?ω?)ノ&/p&&p&如果你的代码出现大量的?.或者!!或者?:,那么你可以考虑用run、let等函数换种写法&/p&&br&&ul&&li&&b&重载companion object的操作符&/b&&/li&&/ul&&p&以下代码(略有修改)摘自&a href=&//link.zhihu.com/?target=https%3A//github.com/Kotlin/kotlin-coroutines& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Kotlin/kotlin-coroutines&/a&,没错就是那个协程库&/p&&figure&&img src=&https://pic4.zhimg.com/v2-f465db007de639c4b8f3f_b.png& data-rawwidth=&1044& data-rawheight=&572& class=&origin_image zh-lightbox-thumb& width=&1044& data-original=&https://pic4.zhimg.com/v2-f465db007de639c4b8f3f_r.png&&&/figure&&p&Channel虽然只是个interface,但是由于companion object的invoke被重载,创建Channel实例(实现类)可以有这么个写法:&/p&&div class=&highlight&&&pre&&code class=&language-kotlin&&&span class=&k&&val&/span& &span class=&py&&channel&/span& &span class=&p&&=&/span& &span class=&n&&Channel&/span&&span class=&p&&&&/span&&span class=&n&&Any&/span&&span class=&p&&&(&/span&&span class=&m&&1&/span&&span class=&p&&)&/span&
&/code&&/pre&&/div&&p&这个companion object还特意带了个名字叫Factory,看出来了吗,没错,这就是工厂模式 (?ω?)ノ&/p&&blockquote&个人习惯尽可能不重载操作符,除非是在写DSL或者用户很聪明&/blockquote&&br&&ul&&li&&b&DSL&/b&&/li&&/ul&&p&DSL算是Kotlin比较有趣的玩法了,比如用&a href=&//link.zhihu.com/?target=https%3A//github.com/Kotlin/anko& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Kotlin/anko&/a&写布局:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-8ebed6f158_b.png& data-rawwidth=&1312& data-rawheight=&1036& class=&origin_image zh-lightbox-thumb& width=&1312& data-original=&https://pic1.zhimg.com/v2-8ebed6f158_r.png&&&/figure&&p&不过Anko还是有不少坑的,并不是那么推荐,平时写个玩具用还是可以的&/p&&br&&p&对于树形结构,DSL表现得不错,比如可以这样子表达一棵协议树:&/p&&figure&&img src=&https://pic1.zhimg.com/v2-d67dc03c2ce_b.png& data-rawwidth=&250& data-rawheight=&242& class=&content_image& width=&250&&&/figure&&p&用操作符inovke重载配合inline lambda就能做出这种效果,没错,上下文也是一层一层打开的&/p&&br&&p&以下代码(略有修改)摘自&a href=&//link.zhihu.com/?target=https%3A//try.kotlinlang.org/%23/Examples/Longer%2520examples/HTML%2520Builder/HTML%2520Builder.kt& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Try Kotlin&/a&,在左边链接里可以在线运行:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-5eacda55989_b.png& data-rawwidth=&606& data-rawheight=&485& class=&origin_image zh-lightbox-thumb& width=&606& data-original=&https://pic2.zhimg.com/v2-5eacda55989_r.png&&&/figure&&p&运行会输出一个段HTML,按这个道理,搞个XML、markdown类似的工具也不是不可以的&/p&&p&用Kotlin配合infix函数搞个SQL或者LINQ之类的玩意,那也不是不可以的 (?ω?)ノ&/p&&br&&ul&&li&&b&reified&/b&&/li&&/ul&&p&代码里的应用场景不算多,举几个库的用法作为例子:&/p&&p&Gson:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&c1&&// Java&/span&
&span class=&n&&UserInfo&/span& &span class=&n&&userInfo&/span& &span class=&o&&=&/span& &span class=&n&&gson&/span&&span class=&o&&.&/span&&span class=&na&&fromJson&/span&&span class=&o&&(&/span&&span class=&n&&jsonString&/span&&span class=&o&&,&/span& &span class=&n&&UserInfo&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&);&/span&
&span class=&c1&&// Kotlin&/span&
&span class=&n&&val&/span& &span class=&n&&userInfo&/span& &span class=&o&&=&/span& &span class=&n&&gson&/span&&span class=&o&&.&/span&&span class=&na&&fromJson&/span&&span class=&o&&&&/span&&span class=&n&&UserInfo&/span&&span class=&o&&&(&/span&&span class=&n&&jsonString&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&&p&Spring:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&c1&&// Java&/span&
&span class=&n&&GenericApplicationContext&/span& &span class=&n&&context&/span& &span class=&o&&=&/span& &span class=&k&&new&/span& &span class=&n&&GenericApplicationContext&/span&&span class=&o&&();&/span&
&span class=&n&&context&/span&&span class=&o&&.&/span&&span class=&na&&registerBean&/span&&span class=&o&&(&/span&&span class=&n&&Foo&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&);&/span&
&span class=&n&&context&/span&&span class=&o&&.&/span&&span class=&na&&registerBean&/span&&span class=&o&&(&/span&&span class=&n&&Bar&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&,&/span& &span class=&o&&()&/span& &span class=&o&&-&&/span& &span class=&k&&new&/span&
&span class=&n&&Bar&/span&&span class=&o&&(&/span&&span class=&n&&context&/span&&span class=&o&&.&/span&&span class=&na&&getBean&/span&&span class=&o&&(&/span&&span class=&n&&Foo&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&))&/span&
&span class=&o&&);&/span&
&span class=&c1&&// Kotlin&/span&
&span class=&n&&val&/span& &span class=&n&&context&/span& &span class=&o&&=&/span& &span class=&n&&GenericApplicationContext&/span& &span class=&o&&{&/span&
&span class=&n&&registerBean&/span&&span class=&o&&&&/span&&span class=&n&&Foo&/span&&span class=&o&&&()&/span&
&span class=&n&&registerBean&/span& &span class=&o&&{&/span& &span class=&n&&Bar&/span&&span class=&o&&(&/span&&span class=&n&&it&/span&&span class=&o&&.&/span&&span class=&na&&getBean&/span&&span class=&o&&&&/span&&span class=&n&&Foo&/span&&span class=&o&&&())&/span& &span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&p&Anko:&/p&&div class=&highlight&&&pre&&code class=&language-java&&&span class=&c1&&// Java&/span&
&span class=&n&&startActivity&/span&&span class=&o&&(&/span&&span class=&n&&Intent&/span&&span class=&o&&(&/span&&span class=&k&&this&/span&&span class=&o&&,&/span& &span class=&n&&OtherActivity&/span&&span class=&o&&.&/span&&span class=&na&&class&/span&&span class=&o&&))&/span&
&span class=&c1&&// Kotlin&/span&
&span class=&n&&startActivity&/span&&span class=&o&&&&/span&&span class=&n&&OtherActivity&/span&&span class=&o&&&()&/span&
&/code&&/pre&&/div&&br&&ul&&li&&b&协程&/b&&/li&&/ul&&p&像&a href=&//link.zhihu.com/?target=https%3A//github.com/Kotlin/ktor& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Kotlin/ktor&/a&就用了协程搞了个HTTP Server轮子,下层可垫个Netty、Tomcat之类轮子&/p&&p&关键的拦截器调用在这里&a href=&//link.zhihu.com/?target=https%3A//github.com/kotlin/ktor/blob/master/ktor-core/src/org/jetbrains/ktor/pipeline/PipelineContext.kt& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Kotlin/ktor&/a&,有兴趣可以研究一下,生产环境就算了,毕竟没有js那么厉害 (逃&/p&&br&&ul&&li&&b&委托&/b&&/li&&/ul&&p&Java是不支持多继承的,常见做法是通过接口和组合来实现类似多继承的效果,Kotlin利用类委托干这种事情时代码量会相对少一丢丢。仔细想想类委托还可以用来实现java.io.XxxStream的那一套装饰器模式呢 (?ω?)ノ&/p&&br&&p&关于属性委托,以下代码摘自&a href=&//link.zhihu.com/?target=https%3A//try.kotlinlang.org/%23/Examples/Delegated%2520properties/Properties%2520in%2520map/Properties%2520in%2520map.kt& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Try Kotlin&/a&,利用Delegate.observable可以获知属性的变化:&/p&&figure&&img src=&https://pic3.zhimg.com/v2-d1ad37b1f83aee_b.png& data-rawwidth=&453& data-rawheight=&200& class=&origin_image zh-lightbox-thumb& width=&453& data-original=&https://pic3.zhimg.com/v2-d1ad37b1f83aee_r.png&&&/figure&&p&想想Android里拿DataBinding做数据绑定为什么要加@Bindable或者用ObserveXxx包装属性,这不就是观察者模式吗,按照这个思路想,是不是就可以开始动手撸一个数据绑定框架了? (?ω?)ノ&/p&&br&&p&以下代码摘自&a href=&//link.zhihu.com/?target=https%3A//try.kotlinlang.org/%23/Examples/Delegated%2520properties/Properties%2520in%2520map/Properties%2520in%2520map.kt& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Try Kotlin&/a&,属性可以通过Map来初始化:&/p&&figure&&img src=&https://pic2.zhimg.com/v2-21dd4dff46cecf1f1fdd_b.png& data-rawwidth=&424& data-rawheight=&219& class=&origin_image zh-lightbox-thumb& width=&424& data-original=&https://pic2.zhimg.com/v2-21dd4dff46cecf1f1fdd_r.png&&&/figure&&p&看起来是不是很眼熟,是不是有点像React的props?包装个Component,配合委托属性做监听、拿扩展函数造DSL,忽然发现自己也是可以撸一个MVVM框架的,想想都觉得有点小激动呢 (?ω?)ノ&/p&&br&&a href=&//link.zhihu.com/?target=https%3A//github.com/Kotlin/kotlin-fullstack-sample& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Kotlin/kotlin-fullstack-sample&/a&&figure&&img src=&https://pic1.zhimg.com/v2-d2ebf55f0b8e0e56934c0_b.png& data-rawwidth=&960& data-rawheight=&1481& class=&origin_image zh-lightbox-thumb& width=&960& data-original=&https://pic1.zhimg.com/v2-d2ebf55f0b8e0e56934c0_r.png&&&/figure&
扩展函数Kotlin大量的语法糖都依靠扩展来实现,代码简洁方面要比Java写个Util类要强。举个例子,String有split操作然而却没有join操作,想要join操作常见的做法是搞个Util类 (比如android.text.TextUtils),如果用Kotlin,可以对Iterable扩展一个joinToStri…
数学的定义其它答案都解释得很多了。这里,我提供一种从具体例子到抽象的解释。希望能帮 monad 除掉「the m-word」的名声。&br&&br&&b&1 为什么说列表是 Monad?&/b&&br&问题标签里有 Scala,这里我们就先来考察 Scala 里的 Seq 是否是 monad。&br&&br&我们来看看 Seq 能干什么:&br&首先,我们可以用&b&一个&/b&元素构造 Seq:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&val&/span& &span class=&n&&persons&/span& &span class=&k&&=&/span& &span class=&nc&&Seq&/span&&span class=&o&&(&/span&&span class=&n&&john&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&注意这里的&b&构造&/b&指:根据&b&一个&/b&元素构造出仅含一个元素的 Seq。&br&&br&第二,我们还可以在一个 Seq 对象上 flatMap(这里顺便提供一个 &a href=&http://zhihu.com/question//answer/& class=&internal&&关于 flatMap 的直观解释&/a&)&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&n&&persons&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&p&/span& &span class=&k&&=&&/span& &span class=&n&&p&/span&&span class=&o&&.&/span&&span class=&n&&favoriteBooks&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&&br&Monad 就是对这两个行为的抽象。我们分别称呼上面两个函数为 &b&id&/b& 和 &b&flatMap&/b&(这里我们不使用 return 和 bind 这两个不直观的名字)。&br&&br&&b&2 &/b&&b&Monad 有什么好?&/b&&br&这样的抽象到底有什么好处?Monad 在数学上的优美这里不重复,请参考其它答案。这里只讲与程序员最相关的一个优点。在一些编程语言里,比如 Scala,Monad 是有专用语法糖的。我们以一个实际例子来说明:&br&&blockquote&假设你有一个语料库,它由不同的文档(类型为 Document)构成,每篇 Document 又有若干句子(类型为 Sentence),每个句子又由词构成(类型为 Word)。即,语料是由 Seq[Document] 组成,Document 里的 sentences 方法返回一个 Seq[Sentence],Sentence 里的 words 方法返回 Seq[Word]。现在你需要获取语料中每个长度大于 4 个字母的词的首字母。&/blockquote&传统的 for 嵌套写法这里就不提了。普遍的写法是利用 flatMap 和 map:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&val&/span& &span class=&n&&lengths&/span& &span class=&k&&=&/span&
&span class=&n&&documents&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&d&/span& &span class=&k&&=&&/span&
&span class=&n&&d&/span&&span class=&o&&.&/span&&span class=&n&&sentences&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&s&/span& &span class=&k&&=&&/span& &span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&n&&words&/span&&span class=&o&&.&/span&&span class=&n&&filter&/span&&span class=&o&&(&/span&&span class=&n&&w&/span& &span class=&k&&=&&/span& &span class=&n&&w&/span&&span class=&o&&.&/span&&span class=&n&&length&/span& &span class=&o&&&&/span& &span class=&mi&&4&/span&&span class=&o&&)))&/span&
&span class=&o&&.&/span&&span class=&n&&map&/span&&span class=&o&&(&/span&&span class=&n&&w&/span& &span class=&k&&=&&/span& &span class=&n&&w&/span&&span class=&o&&(&/span&&span class=&mi&&0&/span&&span class=&o&&))&/span&
&/code&&/pre&&/div&然而这看着依然很乱。有没有更优雅的写法?有!Scala 语言里可以这样:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&for&/span& &span class=&o&&{&/span&
&span class=&n&&d&/span& &span class=&k&&&-&/span& &span class=&n&&documents&/span&
&span class=&n&&s&/span& &span class=&k&&&-&/span& &span class=&n&&d&/span&&span class=&o&&.&/span&&span class=&n&&sentences&/span&
&span class=&n&&w&/span& &span class=&k&&&-&/span& &span class=&n&&s&/span&&span class=&o&&.&/span&&span class=&n&&words&/span&
&span class=&k&&if&/span& &span class=&n&&w&/span&&span class=&o&&.&/span&&span class=&n&&length&/span& &span class=&o&&&&/span& &span class=&mi&&4&/span&
&span class=&o&&}&/span& &span class=&k&&yield&/span& &span class=&n&&w&/span&&span class=&o&&(&/span&&span class=&mi&&0&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&凡是定义有 flatMap 和 map 的类,都可以在 Scala 里这么写。这不是一件激动人心的事情吗?&br&&br&&b&3 到底 &/b&&b&Monad 是什么?&/b&&br&我们可以通过类比来试着猜一下,一个 monad 的 id 和 flatMap 分别具有怎样的参数和返回值。&br&在 Seq 的例子中,id 就是根据&b&一个&/b&元素构造出 Seq 的函数,即&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&def&/span& &span class=&n&&id&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&](&/span&&span class=&n&&x&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span&&span class=&o&&)&/span&&span class=&k&&:&/span& &span class=&kt&&Seq&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&]&/span& &span class=&k&&=&/span& &span class=&nc&&Seq&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&如果我们把 Seq 的 flatMap 改写成一个全局函数,它会是这样&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&def&/span& &span class=&n&&flatMap&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&, &span class=&kt&&Y&/span&&span class=&o&&](&/span&&span class=&n&&xs&/span&&span class=&k&&:&/span& &span class=&kt&&Seq&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&],&/span& &span class=&n&&f&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span& &span class=&o&&=&&/span& &span class=&nc&&Seq&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&])&/span&&span class=&k&&:&/span& &span class=&kt&&Seq&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&]&/span& &span class=&k&&=&/span& &span class=&n&&xs&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&f&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&&br&好,现在我们来抽象一下这两个函数。假设现在有一种抽象的、行为类似 Seq 的类型,叫 M。在它上面我们可以类比 Seq,定义出抽象的 id 和 flatMap:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&def&/span& &span class=&n&&id&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&](&/span&&span class=&n&&x&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span&&span class=&o&&)&/span&&span class=&k&&:&/span& &span class=&kt&&M&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&]&/span&
&span class=&k&&def&/span& &span class=&n&&flatMap&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&, &span class=&kt&&Y&/span&&span class=&o&&](&/span&&span class=&n&&xs&/span&&span class=&k&&:&/span& &span class=&kt&&M&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&],&/span& &span class=&n&&f&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span& &span class=&o&&=&&/span& &span class=&n&&M&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&])&/span&&span class=&k&&:&/span& &span class=&kt&&M&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&]&/span&
&/code&&/pre&&/div&看,与具体的 Seq 的 id 和 flatMap 没有太大区别。事实上,列表就是最天然的 monad. &br&&br&有了这两个函数的确切签名,我们就可以给出 monad 的定义了:&br&&blockquote&&b&能定义出上面两个函数的类型 M 就是 monad。&/b&&/blockquote&当然,这两个函数还必须满足一些 monad 公理 &del&这里暂不关心&/del&(见文末更新)。然而类型系统无法验证这些性质,实际使用中都是程序员自觉。&br&&br&具体到代码上,我们可以用下面这个 typeclass 来定义:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&trait&/span& &span class=&nc&&Monad&/span&&span class=&o&&[&/span&&span class=&kt&&M&/span&&span class=&o&&[&/span&&span class=&k&&_&/span&&span class=&o&&]]&/span& &span class=&o&&{&/span&
&span class=&k&&def&/span& &span class=&n&&id&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&](&/span&&span class=&n&&x&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span&&span class=&o&&)&/span&&span class=&k&&:&/span& &span class=&kt&&M&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&]&/span&
&span class=&k&&def&/span& &span class=&n&&flatMap&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&, &span class=&kt&&Y&/span&&span class=&o&&](&/span&&span class=&n&&xs&/span&&span class=&k&&:&/span& &span class=&kt&&M&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&],&/span& &span class=&n&&f&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span& &span class=&o&&=&&/span& &span class=&n&&M&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&])&/span&&span class=&k&&:&/span& &span class=&kt&&M&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&]&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&&b&4 &/b&&b&还有哪些 Monad 的实例?&/b&&br&有了这个定义,我们会瞬间发现,其实 Monad 无处不在。除了像 Seq 那样的列表类型,这里再多考察几个其它类型的。&br&&br&&b&4.1 &/b&&b&Option Monad&/b&&br&我们来看看许多函数式语言里都有的 Option 是否是 monad。方便起见,这里仍然用 Scala 语言。我们试试能不能定义出 Option 的 id 和 flatMap:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&def&/span& &span class=&n&&id&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&](&/span&&span class=&n&&x&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span&&span class=&o&&)&/span&&span class=&k&&:&/span& &span class=&kt&&Option&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&]&/span& &span class=&k&&=&/span& &span class=&nc&&Some&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&)&/span&
&span class=&k&&def&/span& &span class=&n&&flatMap&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&, &span class=&kt&&Y&/span&&span class=&o&&](&/span&&span class=&n&&ox&/span&&span class=&k&&:&/span& &span class=&kt&&Option&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&],&/span& &span class=&n&&f&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span& &span class=&o&&=&&/span& &span class=&nc&&Option&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&])&/span&&span class=&k&&:&/span& &span class=&kt&&Option&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&]&/span& &span class=&k&&=&/span&
&span class=&n&&os&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&f&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&看,我们成功定义出了这两个函数。所以 Option 是 monad。我们说明这个这又有什么用?&br&&br&来,我们看看,究竟什么时候需要 Option?有返回 null 的需求,但不想看到 NullPointerException 时。比如 Java 代码中经常出现下面这种代码:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&person&/span&&span class=&o&&.&/span&&span class=&n&&bestFriend&/span& &span class=&o&&!=&/span& &span class=&kc&&null&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&if&/span& &span class=&o&&(&/span&&span class=&n&&person&/span&&span class=&o&&.&/span&&span class=&n&&bestFriend&/span&&span class=&o&&.&/span&&span class=&n&&favoriteBook&/span& &span class=&o&&!=&/span& &span class=&kc&&null&/span&&span class=&o&&)&/span& &span class=&o&&{&/span&
&span class=&k&&return&/span& &span class=&cm&&/* anything */&/span&
&span class=&o&&}&/span&
&span class=&k&&else&/span& &span class=&k&&return&/span& &span class=&kc&&null&/span&
&span class=&o&&}&/span&
&span class=&k&&else&/span& &span class=&k&&return&/span& &span class=&kc&&null&/span&
&/code&&/pre&&/div&为了拯救这种代码,我们需要引入 Option。本来 person.bestFriend 返回的要么是一个 Person 对象,要么是一个邪恶的 null。我们可以将其返回类型改为 Option[Person]。关于 Option 是什么,请参考任意有 Option 的标准库文档。&br&有了 Option 是 monad 的证据,我们就可以写出如下的代码了:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&for&/span& &span class=&o&&{&/span&
&span class=&n&&bf&/span& &span class=&k&&&-&/span& &span class=&n&&person&/span&&span class=&o&&.&/span&&span class=&n&&bestFriend&/span&
&span class=&n&&fb&/span& &span class=&k&&&-&/span& &span class=&n&&bf&/span&&span class=&o&&.&/span&&span class=&n&&favoriteBook&/span&
&span class=&o&&}&/span& &span class=&k&&yield&/span& &span class=&cm&&/* whatever */&/span&
&/code&&/pre&&/div&看,既不会导致运行时 NullPointer 错误,又不用手动检查 null,代码又易读。&br&&br&&b&4.2 &/b&&b&分布 Monad&/b&&br&为了深化对 monad 的理解,我们再来&i&丧病&/i&地考察分布(Distribution),看看它是不是个 monad。&br&我们明确一下 Distribution 到底是个什么东西。这里用 Scala 的 trait 定义:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&trait&/span& &span class=&nc&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&+X&/span&&span class=&o&&]&/span& &span class=&o&&{&/span& &span class=&n&&outer&/span& &span class=&k&&=&&/span&
&span class=&k&&def&/span& &span class=&n&&sample&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span&
&span class=&k&&def&/span& &span class=&n&&flatMap&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&](&/span&&span class=&n&&f&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span& &span class=&o&&=&&/span& &span class=&nc&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&])&/span&&span class=&k&&:&/span& &span class=&kt&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&]&/span& &span class=&k&&=&/span&
&span class=&k&&new&/span& &span class=&nc&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&]&/span& &span class=&o&&{&/span&
&span class=&k&&def&/span& &span class=&n&&sample&/span&&span class=&k&&:&/span& &span class=&kt&&Y&/span& &span class=&o&&=&/span& &span class=&n&&f&/span&&span class=&o&&(&/span&&span class=&n&&outer&/span&&span class=&o&&.&/span&&span class=&n&&sample&/span&&span class=&o&&).&/span&&span class=&n&&sample&/span&
&span class=&o&&}&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&有了这个定义,我们很容易就能写出 id 和 flatMap 的 Distribution 版本:&br&我们先写出 flatMap。直接使用 Distribution 里的 flatMap 即可:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&def&/span& &span class=&n&&flatMap&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&, &span class=&kt&&Y&/span&&span class=&o&&](&/span&&span class=&n&&xs&/span&&span class=&k&&:&/span& &span class=&kt&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&],&/span& &span class=&n&&f&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span& &span class=&o&&=&&/span& &span class=&nc&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&Y&/span&&span class=&o&&])&/span& &span class=&k&&=&/span&
&span class=&n&&xs&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&f&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&&br&然后是 id。函数 id 的作用就是:接收一个样本,构造出一个分布。那么一个样本能构造出来什么分布呢?答案就是抽样时永远返回同一个样本的分布:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&def&/span& &span class=&n&&id&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&](&/span&&span class=&n&&x&/span&&span class=&k&&:&/span& &span class=&kt&&X&/span&&span class=&o&&)&/span&&span class=&k&&:&/span& &span class=&kt&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&X&/span&&span class=&o&&]&/span& &span class=&k&&=&/span& &span class=&k&&new&/span& &span class=&nc&&Distribution&/span& &span class=&o&&{&/span&
&span class=&k&&def&/span& &span class=&n&&sample&/span&&span class=&k&&:&/span& &span class=&kt&&T&/span& &span class=&o&&=&/span& &span class=&n&&x&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&因此,我们意识到,分布也是 monad!&br&&br&我们来定义两个常用的分布:Uniform 和 Gaussian:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&case&/span& &span class=&k&&class&/span& &span class=&nc&&Uniform&/span&&span class=&o&&(&/span&&span class=&n&&a&/span&&span class=&k&&:&/span& &span class=&kt&&Double&/span&&span class=&o&&,&/span& &span class=&n&&b&/span&&span class=&k&&:&/span& &span class=&kt&&Double&/span&&span class=&o&&)&/span& &span class=&k&&extends&/span& &span class=&nc&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&Double&/span&&span class=&o&&]&/span& &span class=&o&&{&/span&
&span class=&k&&def&/span& &span class=&n&&sample&/span& &span class=&k&&=&/span& &span class=&n&&scala&/span&&span class=&o&&.&/span&&span class=&n&&util&/span&&span class=&o&&.&/span&&span class=&nc&&Random&/span&&span class=&o&&.&/span&&span class=&n&&nextDouble&/span&&span class=&o&&()&/span& &span class=&o&&*&/span& &span class=&o&&(&/span&&span class=&n&&b&/span& &span class=&o&&-&/span& &span class=&n&&a&/span&&span class=&o&&)&/span& &span class=&o&&+&/span& &span class=&n&&a&/span&
&span class=&o&&}&/span&
&span class=&k&&case&/span& &span class=&k&&class&/span& &span class=&nc&&Gaussian&/span&&span class=&o&&(&/span&&span class=&n&&μ&/span&&span class=&k&&:&/span& &span class=&kt&&Double&/span&&span class=&o&&,&/span& &span class=&n&&σ2&/span&&span class=&k&&:&/span& &span class=&kt&&Double&/span&&span class=&o&&)&/span& &span class=&k&&extends&/span& &span class=&nc&&Distribution&/span&&span class=&o&&[&/span&&span class=&kt&&Double&/span&&span class=&o&&]&/span& &span class=&o&&{&/span&
&span class=&k&&def&/span& &span class=&n&&sample&/span& &span class=&k&&=&/span& &span class=&n&&scala&/span&&span class=&o&&.&/span&&span class=&n&&util&/span&&span class=&o&&.&/span&&span class=&nc&&Random&/span&&span class=&o&&.&/span&&span class=&n&&nextGaussian&/span&&span class=&o&&()&/span& &span class=&o&&*&/span& &span class=&n&&math&/span&&span class=&o&&.&/span&&span class=&n&&sqrt&/span&&span class=&o&&(&/span&&span class=&n&&σ2&/span&&span class=&o&&)&/span& &span class=&o&&+&/span& &span class=&n&&μ&/span&
&span class=&o&&}&/span&
&/code&&/pre&&/div&&br&哈哈,展示 monad 的威力的时候到了。我们可以非常自然地用现有分布定义一个新分布:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&k&&val&/span& &span class=&n&&d&/span& &span class=&k&&=&/span& &span class=&k&&for&/span& &span class=&o&&{&/span&
&span class=&n&&x&/span& &span class=&k&&&-&/span& &span class=&nc&&Uniform&/span&&span class=&o&&(&/span&&span class=&mi&&0&/span&&span class=&o&&,&/span& &span class=&mi&&2&/span&&span class=&o&&)&/span&
&span class=&n&&y&/span& &span class=&k&&&-&/span& &span class=&nc&&Gaussian&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&,&/span& &span class=&mi&&1&/span&&span class=&o&&)&/span&
&span class=&o&&}&/span& &span class=&k&&yield&/span& &span class=&n&&x&/span& &span class=&o&&+&/span& &span class=&n&&y&/span&
&/code&&/pre&&/div&就像写数学公式一样自然。&br&&br&Monad 为我们带来了类型安全,能让我们的程序易读。请不要以「the m-word」称呼它。&br&&br&&b&------- UPDATE --------&/b&&br&还是把 Monad 对 id 和 flatMap 的要求(即 Monad 公理)写出来吧:&br&第一条:id 是 flatMap 的左单位元&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&id&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&),&/span& &span class=&n&&f&/span&&span class=&o&&)&/span& &span class=&n&&等于&/span& &span class=&n&&f&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&)&/span&
&/code&&/pre&&/div&第二条:id 是 flatMap 的右单位元&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&xs&/span&&span class=&o&&,&/span& &span class=&n&&id&/span&&span class=&o&&)&/span& &span class=&n&&等于&/span& &span class=&n&&xs&/span&
&/code&&/pre&&/div&第三条:结合律&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&m&/span&&span class=&o&&,&/span& &span class=&n&&f&/span&&span class=&o&&),&/span& &span class=&n&&g&/span&&span class=&o&&)&/span& &span class=&n&&等于&/span& &span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&m&/span&&span class=&o&&,&/span& &span class=&n&&x&/span& &span class=&k&&=&&/span& &span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&f&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&),&/span& &span class=&n&&g&/span&&span class=&o&&))&/span&
&/code&&/pre&&/div&&br&当然,写成面向对象的形式可能更好理解:&br&&div class=&highlight&&&pre&&code class=&language-scala&&&span class=&n&&id&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&).&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&f&/span&&span class=&o&&)&/span&
&span class=&n&&等于&/span& &span class=&n&&f&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&)&/span&
&span class=&n&&xs&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&id&/span&&span class=&o&&)&/span&
&span class=&n&&等于&/span& &span class=&n&&xs&/span&
&span class=&n&&xs&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&f&/span&&span class=&o&&).&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&g&/span&&span class=&o&&)&/span& &span class=&n&&等于&/span& &span class=&n&&xs&/span&&span class=&o&&.&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&x&/span& &span class=&k&&=&&/span& &span class=&n&&f&/span&&span class=&o&&(&/span&&span class=&n&&x&/span&&span class=&o&&).&/span&&span class=&n&&flatMap&/span&&span class=&o&&(&/span&&span class=&n&&g&/span&&span class=&o&&))&/span&
&/code&&/pre&&/div&
数学的定义其它答案都解释得很多了。这里,我提供一种从具体例子到抽象的解释。希望能帮 monad 除掉「the m-word」的名声。 1 为什么说列表是 Monad? 问题标签里有 Scala,这里我们就先来考察 Scala 里的 Seq 是否是 monad。 我们来看看 Seq 能干什么: 首…
&figure&&img src=&https://pic3.zhimg.com/4ff90f532b47f7a5ecacc_b.jpg& data-rawwidth=&556& data-rawheight=&295& class=&origin_image zh-lightbox-thumb& width=&556& data-original=&https://pic3.zhimg.com/4ff90f532b47f7a5ecacc_r.jpg&&&/figure&&h2&FBI WARNING&/h2&&p&本文一不小心又被我写长了,代码量也比较大,为了保证阅读体验,请最好在pc浏览器上观看,谢谢配合&/p&&p&本文是想探讨一下Free Monad & Applicative 以及在实际中的应用,以及若干脑洞&/p&&h2&Free Monads Are Simple&/h2&&p&Free Monad的打开方式,非常非常非常简单,和把大象装进冰箱差不多。找好你的座位,我们通过一个简单的例子(抄的underscore上的例子)感受一下:&/p&&p&第一步,定义需要被lift的ADT,以及数据类型若干:&br&&/p&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//data
case class Tweet(userId: Int, msg: String)
case class User(id: Int, name: String, photo: String)
//Service代表了Fetch的具体操作/命令
sealed trait Service[A] //A为result type
final case class GetTweets(userId: Int) extends Service[List[Tweet]]
final case class GetUserName(userId: Int) extends Service[String]
final case class GetUserPhoto(userId: Int) extends Service[String]
//the ADT need lift to free monad
sealed trait Request[A]
case class Pure[A](a: A) extends Request[A]
case class Fetch[A](service: Service[A]) extends Request[A]
&/code&&/pre&&/div&&/div&&p&第二步,定义lift方法
把ADT转换为Free Monad&br&&/p&&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&type Requestable[A] = Coyoneda[Request, A] //Coyoneda 后面会说
object Request { //lift to Free
def pure[A](a: A): Free[Requestable, A] = Free.liftFC(Pure(a) : Request[A])
def fetch[A](service: Service[A]): Free[Requestable, A] =
Free.liftFC(Fetch(service) : Request[A])
&/code&&/pre&&/div&&/div&&p&第三步,定义解释器 (其实就是个自然变换,将F[A]转为另一个Monad)&/p&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//interpreter
object ToyInterpreter extends (Request ~& Id.Id) {
import Id._
def apply[A](in: Request[A]): Id[A] =
in match {
case Pure(a) =& a
case Fetch(service) =&
service match {
case GetTweets(userId) =&
println(s&Getting tweets for user $userId&)
List(Tweet(1, &Hi&), Tweet(2, &Hi&), Tweet(1, &Bye&))
case GetUserName(userId) =&
println(s&Getting user name for user $userId&)
userId match {
case 1 =& &Agnes&
case 2 =& &Brian&
case _ =& &Anonymous&
case GetUserPhoto(userId) =&
println(s&Getting user photo for user $userId&)
userId match {
case 1 =& &:-)&
case 2 =& &:-D&
case _ =& &:-|&
&/code&&/pre&&/div&&/div&&p&第四步,编写AST(for-comprehension)以及运行解释器 &/p&&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&//AST
import Request._
def getUser(id: Int): Free[Requestable, User] =
&- fetch(GetUserName(id))
photo &- fetch(GetUserPhoto(id))
} yield User(id, name, photo)
val user: User = Free.runFC(getUser(1))(ToyInterpreter)
&/code&&/pre&&/div&&/div&&p&Free Monad就是 AST + Interpreter,将任意的type为F[A]的ADT转变为Free Monad的AST,就可以随意解释执行了,相同的AST可以有不同的Interpreter,就像同一个interface有不同的implements。&br&&/p&&h2&Yoneda Lemma&/h2&&figure&&img src=&https://pic3.zhimg.com/cd15a97bfd0f_b.jpg& data-rawwidth=&600& data-rawheight=&313& width=&600& data-original=&https://pic3.zhimg.com/cd15a97bfd0f_r.jpg& class=&origin_image zh-lightbox-thumb&&&/figure&&blockquote&Yoneda's Lemma (米田引理,得名于日本计算机科学家米田信夫) 是一个对一般的范畴无条件成立的引理。说的是可表函子&img src=&https://www.zhihu.com/equation?tex=h_%7BA%7D%5E%7B%5Ccirc+%7D+%3DHom%28A%2C+-%29& alt=&h_{A}^{\circ } =Hom(A, -)& eeimg=&1&&到一般的取值在集合范畴的函子&img src=&https://www.zhihu.com/equation?tex=F& alt=&F& eeimg=&1&&&br&之间的自然变换,典范同构于&img src=&https://www.zhihu.com/equation?tex=F%28A%29& alt=&F(A)& eeimg=&1&&,集合间的同构,无非是一个一一对应的映射。&/blockquote&hom-functor与(&strong&Set&/strong&-valued:取值在集合范畴的)函子F的自然变换,写成scala就是:def map[B](f: A =& B): F[B], 它与F[A]自然同购。 &p&Yoneda可写为:&/p&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&trait Yoneda[F[_], A] { def map[B](f: A =& B): F[B] }
&/code&&/pre&&/div&&/div&&p&其与F[A]的iso(同构)可以写为:&br&&/p&&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def toYo[F[_]:Functor,A](fa: F[A]) = new Yoneda[F,A] {
def map[B](f: A =& B) = Functor[F].map(fa)(f)
def froYo[F[_],A](yo: Yoneda[F,A]) = yo.map(a =& a) //f: A =& B 传入id时,直接得到F[A]的实例(就是上图的u)
&/code&&/pre&&/div&&/div&CoYoneda则是,将协变(a -& b)变成逆变 (b -& a) &br&(b -& a) -& F b ? F a&br&
可表示为:&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&trait CoYoneda[F[_], A] {
def f: B =& A
def fi: F[B]
&/code&&/pre&&/div&&/div&
CoYoneda[F,A] 与 F[A]的iso可写为:&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def toCoYo[F[_],A](fa: F[A]) = new CoYoneda[F,A] {
type B = A
val f = (a: A) =& a
val fi = fa
def froCoYo[F[_]:Functor,A](yo: CoYoneda[F,A]) = Functor[F].map(yo.fi)(yo.f)
&/code&&/pre&&/div&&/div&
Yoneda[F, A]和Coyoneda[F, A]都和F[A]是同构的,但是toCoYo 并不要求F[A]是个Functor(看toCoYo方法),而Coyoneda是个Functor(因为同构)&br&
可以看Scalaz的这段注释:
&br&&blockquote&The dual view of the Yoneda lemma. Also a &strong&free functor&/strong& on F. This is isomorphic to F as long as F itself is a functor. The homomorphism from F[A] to Coyoneda[F,A] exists even when F is not a functor.
&/blockquote&这是我见过,米田引理除了米田嵌入,CPS之外最重要的应用了。&br&Free Monad Free[F[_], A]要求其F是个Functor,一般的ADT不具备Functor性质,所以先转为CoYoneda(free functor)&br&&h2&Free Monad原理&/h2&&blockquote&Free[F,A] is a type of “leafy tree” that branches according to F, with values of type A at the leaves.&br&All monads can model some kind of leafy tree structure&/blockquote&Free monad的结构如下:
&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&sealed trait Free[F[_], A]
case class Return[F[_], A](a: A) extends Free[F, A]
case class Suspend[F[_], A](s: F[Free[F, A]]) extends Free[F, A]
&/code&&/pre&&/div&&/div&也有另一种结构:&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&sealed trait Free[F[_],A]
case class Return[F[_],A](a: A) extends Free[F,A]
case class Bind[F[_],I,A](req: F[I], k: I =& Free[F,A]) extends IO[F,A]
&/code&&/pre&&/div&&/div&
这种结构比较好说明,我们把Return映射为List的Nil, Bind对应于Cons,那么req就是head, k就是rest:&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
Return //求值的时候是先从外面解开,所以Return是root
&/code&&/pre&&/div&&/div&而Bind和Suspend是等价的:(F[I], I =& Free[F,A]) + Coyoneda == (F[Free[F, A]]) 后者要求F是个Functor&br&
因为Bind对非尾递归不友好,所以在Scala里用Suspend的形式(这个就得参考Trampoline了&a href=&https://link.zhihu.com/?target=http%3A//blog.higher-order.com/assets/trampolines.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&http://http://blog.higher-order.com/assets/trampolines.pdf&/a&)。&br&&h2&多个ADT怎么办?&/h2&比如下面的三种ADT,他们该怎么交互呢?&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&sealed trait Reader[+A]
case class Ask(r: String) extends Reader[Int]
sealed trait Writer[+A]
case class Tell(a: Int) extends Writer[List[Int]]
sealed trait Console[+A]
case class Println(a: List[Int]) extends Console[Unit]
&/code&&/pre&&/div&&/div&&figure&&img src=&https://pic3.zhimg.com/100a7e7345764eca329fc1ff07b916f5_b.jpg& data-rawwidth=&471& data-rawheight=&354& width=&471& data-original=&https://pic3.zhimg.com/100a7e7345764eca329fc1ff07b916f5_r.jpg& class=&origin_image zh-lightbox-thumb&&&/figure&&b&Coproduct &/b&&br&我们把上面三种F[A]给加到一起变成一个类型就行了,Sum[A] = Reader[A] + Writer[A] + Console[A],这个Sum就是Coproduct。&br&Scalaz 提供了Coproduct[F[_], G[_], A]和Inject[F[_],G[_]]类型,前者表示把F和G的和类型,后者用来将F注入到G类型中,通过简单的例子感受一下:&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&import scalaz._ , scalaz.{Coproduct =& Coproductz}
import Scalaz._
def lift[F[_],G[_],A](fa: F[A])(implicit I: Inject[F,G]): G[A] = I.inj(fa)
type ListorOption[A] = Coproductz[List, Option, A]
val c1 = lift[Option,ListorOption,String](Some(&blah&))
val c2 = lift[List,ListorOption,String](List(&blah&))
type ListorOptionorFutrue[A] = Coproductz[ListorOption, Future, A]
val c3 = lift[ListorOption,ListorOptionorFutrue,String](c1)
val c4 = lift[Future,ListorOptionorFutrue,String](Future.now(&a&))
val rc: String = c3.run match {
case \/-(f) =& f.run
case -\/(lo) =& lo.run match {
case \/-(o) =& o.getOrElse(&&)
case -\/(l) =& l.mkString
&/code&&/pre&&/div&&/div&&p&所以我们先将三种ADT都注入到Coproduct中,变成一种ADT(Coproduct[_, _, A]),(吐槽:说老实话,Scalaz的Coproduct实现真没Shapeless的好)&/p&&p&上面三种ADT的完整代码(可以跳过):&/p&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def lift2Coproduct[F[_], G[_], A](fa: F[A])(implicit I: Inject[F, G]) = I.inj(fa)
implicit def lift2FreeC[G[_], A](ga: G[A]): Free.FreeC[G, A] = Free.liftFC(ga)
type ReaderOrWriter[A] = Coproduct[Reader, Writer, A]
type ReaderOrWriterOrConsole[A] = Coproduct[ReaderOrWriter, Console, A]
def ask(r: String) = lift2Coproduct[ReaderOrWriter, ReaderOrWriterOrConsole, Int](lift2Coproduct[Reader, ReaderOrWriter, Int](Ask(r)))
def tell(a: Int) = lift2Coproduct[ReaderOrWriter, ReaderOrWriterOrConsole, List[Int]](lift2Coproduct[Writer, ReaderOrWriter, List[Int]](Tell(a)))
def cprintln(a: List[Int]) = lift2Coproduct[Console, ReaderOrWriterOrConsole, Unit](Println(a))
type FreeRWC[A] = FreeC[ReaderOrWriterOrConsole, A]
def program =
uid &- ask(&id&): FreeRWC[Int]
l &- tell(uid): FreeRWC[List[Int]]
_ &- cprintln(l): FreeRWC[Unit]
object ReaderEff extends (Reader ~& Id) {
def apply[A](ia: Reader[A]): Id[A] = ia match {
case Ask(p) =& 1001
object WriterEff extends (Writer ~& Id) {
def apply[A](ia: Writer[A]): Id[A] = ia match {
case Tell(p) =& List(p)
object ConsoleEff extends (Console ~& Id) {
def apply[A](ia: Console[A]): Id[A] = ia match {
case Println(p) =& println(p); ()
//解Coproduct
def or[F[_], G[_], H[_]](fg: F ~& G, hg: H ~& G): ({type l[x] = Coproduct[F, H, x]})#l ~& G = new (({type l[x] = Coproduct[F, H, x]})#l ~& G) {
def apply[A](ca: Coproduct[F, H, A]): G[A] = ca.run match {
case -\/(fa) =& fg(fa)
case \/-(ha) =& hg(ha)
val x: ReaderOrWriter ~& Id = or(ReaderEff, WriterEff)
val y: ReaderOrWriterOrConsole ~& Id = or(x, ConsoleEff)
val r = Free.runFC(program)(y)
&/code&&/pre&&/div&&/div&&p&OK,这样就完备了&/p&&h2&Free Applicative
&/h2&&p&Free applicative是和Free Monad类似的东西,只不过它翻译为的是个applicative而已,最新版的Scalaz已经有FreeAp了,感受一下:&/p&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&sealed trait ParseOp[A]
case class ParseInt(key: String) extends ParseOp[Int]
case class ParseBool(key: String) extends ParseOp[Boolean]
// 定义lift
// Free applicative over Parse.
type Parse[A] = FreeAp[ParseOp, A]
// Smart constructors for Parse[A]
def parseInt(key: String) = FreeAp.lift(ParseInt(key))
def parseBool(key: String) = FreeAp.lift(ParseBool(key))
// Natural transformation to Option[A]
object ToOption extends (ParseOp ~& Option) {
def toIntOption(number: String) = try Some(number.trim.toInt) catch { case _: Exception =& None }
def toBooleanOption(bool: String) = try Some(bool.trim.toBoolean) catch { case _: Exception =& None }
def apply[A](fa: ParseOp[A]) = fa match {
case ParseInt(key) =& toIntOption(key)
case ParseBool(key) =& toBooleanOption(key)
val successfulProg: Parse[(Int, Boolean)] = (parseInt(&1&) |@| parseBool(&true&))((_, _))
val failedProg: Parse[(Boolean, Int)] = (parseBool(&false&) |@| parseInt(&bool&))((_, _))
val res1 = successfulProg.foldMap(ToOption) //Some((1,true))
val res2 = failedProg.foldMap(ToOption) //None
&/code&&/pre&&/div&&/div&这是Free applicative的结构:&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&sealed abstract class FreeAp[F[_],A]
class Pure[F[_],A](a: A) extends FreeAp[F,A]
case class Ap[F[_],A]() extends FreeAp[F,A] {
val v: () =& F[I]
val k: () =& FreeAp[F, I =& A]
&/code&&/pre&&/div&&/div&&p&原理不多说了,和Free Monad类似&/p&&br&因为applicative可表示 independent computation,也就是上面的例子中|@| 左边和右边是可以同时执行的,因为它们没有依赖性。&br&Scalaz的实现是基于lazy和线性的,不会提供并行,但是我们可以自己搞一个;我们可以用Scala Future来举个例子,因为它是个monad(肯定也是个applicative):&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&implicit val futureApplicative = new Applicative[Future] {
def point[A](a: =& A) = Future(a)
def ap[A, B](ca: =& Future[A])(cfab: =& Future[A =& B]): Future[B] =
cfab map {fab =& fab(Await.result(ca, Duration.Inf))}
val f1 = Future(1)
val f2 = Future(2)
def f3(a: Int) = Future(a + 1)
val r = for {
r1 &- (f1 |@| f2)(_ + _)
r2 &- f3(r1)
} yield r1 + r2
&/code&&/pre&&/div&&/div&发现一个有趣的事情,那就是monad可以用来组合依赖的计算(纵向组合),applicative用来做并行计算(横向组合),上面的f1和f2会同时去做,做完后才会去做f3,f3依赖值r1&br&同理我们把我们的Free Monad结构改一下,把叶子F[A]改为F[A]的Monoid(比如list):&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&
[F[A]...] Bind -------------layer1
Return --------layer2
&/code&&/pre&&/div&&/div&这样在原来依赖关系(monad)不变的情况下,每层的list都是可以并行计算(applicative)的。&br&这样就让任意的类型为F[A]的ADT具备了并行运算的能力,相当于套了一层并行运算的context。&br&这个想法来自一个叫Simon Marlow的人,这是他的安利slide:&a href=&https://link.zhihu.com/?target=http%3A//www.cs.ox.ac.uk/ralf.hinze/WG2.8/31/slides/simon.pdf& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&http://www.http://cs.ox.ac.uk/ralf.hinze/WG2.8/31/slides/simon.pdf&/a&&br&&br&当时对着slide撸了一个,不要闲丑:&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&def freeApplicative[F[_]] = new Applicative[({type f[A]=Free[F,A]})#f] {
def unit[A](a: A): Free[F, A] = Done(a)
def map2[A, B, C](mb1: Free[F, A], mb2: Free[F, B])(f: (A, B) =& C)(implicit M: Monoid[F[_]]): Free[F, C] = (mb1, mb2) match {
case (Done(a), Done(b)) =&
Done(f(a, b))
case (Done(a1), Blocked(reqs, cont)) =&
Blocked(reqs, (x: Any) =& Done(a1).map2(cont(x))(f))
case (Blocked(reqs, cont), Done(b1)) =&
Blocked(reqs, (x: Any) =& cont(x).map2(Done(b1))(f))
case (Blocked(reqs1, cont1), Blocked(reqs2, cont2)) =&
Blocked(M.op(reqs1, reqs2), (x: Any) =& cont1(x).map2(cont2(x))(f))
&/code&&/pre&&/div&&/div&Done 和Blocked对应上面的Return和Bind,在写Interpreter(自然变换)的时候,对这个Monoid(如list) 并行执行后转换为Responses(一个Request -& Any 的Map,方便做缓存)&br&&div class=&highlight&&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&val f: F[_] =& Responses[Request] = r =& {
println(s&--${Thread.currentThread().getName}&)
service.deal(r) //service不一定在本地
def fetch(l: List[Request[_]]): Responses[Request] = {
l.par.map(x =& f(x.asInstanceOf[F[_]])).fold(Responses.Monoid.zero)(Responses.Monoid.op) //par是并发执行
&/code&&/pre&&/div&&/div&&h2&Distributed&/h2&在boss的提醒下,AST上的节点,不一定需要在本地执行,可以交给Akka集群,远端执行,就是把fetch方法里的x分发出去。 Scala具备序列化闭包的能力(不过得如外科手术般小心), 甚至可以将整个AST发到远端去执行(这是个脑洞,详情见评论区)。是不是英吹四艇?&br&&br&至此,敬礼,感谢观看&br&&br&Ref:&br&&a href=&https://link.zhihu.com/?target=http%3A//underscore.io/blog/posts//free-monads-are-simple.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Free Monads Are Simple&/a&&br&&a href=&https://link.zhihu.com/?target=http%3A//underscore.io/blog/posts//deriving-the-free-monad.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Deriving the Free Monad&/a&
FBI WARNING本文一不小心又被我写长了,代码量也比较大,为了保证阅读体验,请最好在pc浏览器上观看,谢谢配合本文是想探讨一下Free Monad & Applicative 以及在实际中的应用,以及若干脑洞Free Monads Are SimpleFree Monad的打开方式,非常非常非常简单,…
【转载】傻瓜函数编程&br&原始链接:&br&&a href=&//link.zhihu.com/?target=https%3A//github.com/justinyhuang/Functional-Programming-For-The-Rest-of-Us-Cn& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&justinyhuang/Functional-Programming-For-The-Rest-of-Us-Cn · GitHub&/a&&br&&br&开篇&p&我们这些码农做事都是很拖拉的。每天例行报到后,先来点咖啡,看看邮件还有RSS订阅的文章。然后翻翻新闻还有那些技术网站上的更新,再过一遍编程论坛口水区里那些无聊的论战。最后从头把这些再看一次以免错过什么精彩的内容。然后就可以吃午饭了。饭饱过后,回来盯着IDE发一会呆,再看看邮箱,再去搞杯咖啡。光阴似箭,可以回家了……&br&(在被众人鄙视之前)我唯一想说的是,在这些拖拉的日子里总会时不时读到一些&a href=&//link.zhihu.com/?target=http%3A//www.baike.com/wiki/%25E4%25B8%258D%25E6%E8%25A7%%258E%2589& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&不明觉厉&/a&的文章。如果没有打开不应该打开的网站,每隔几天你都可以看到至少一篇这样的东西。它们的共性:难懂,耗时,于是这些文章就慢慢的堆积成山了。很快你就会发现自己已经累积了一堆的收藏链接还有数不清的PDF文件,此时你只希望隐入一个杳无人烟的深山老林里什么也不做,用一年半载好好的消化这些私藏宝贝。当然,我是说最好每天还是能有人来给送吃的顺带帮忙打扫卫生倒垃圾,哇哈哈。&/p&&p&我不知道你都收藏了些什么,我的阅读清单里面相当大部分都是函数式编程相关的东东:基本上是最难啃的。这些文章充斥着无比枯燥的教科书语言,我想就连那些在华尔街浸淫10年以上的大牛都无法搞懂这些函数式编程(简称FP)文章到底在说什么。你可以去花旗集团或者德意志银行找个项目经理来问问1:你们为什么要选JMS而不用Erlang?答案基本上是:我认为这个学术用的语言还无法胜任实际应用。可是,现有的一些系统不仅非常复杂还需要满足十分严苛的需求,它们就都是用函数式编程的方法来实现的。这,就说不过去了。&br&关于FP的文章确实比较难懂,但我不认为一定要搞得那么晦涩。有一些历史原因造成了这种知识断层,可是FP概念本身并不难理解。我希望这篇文章可以成为一个“FP入门指南”,帮助你从&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/zh/%25E6%258C%%25BB%25A4%25E5%25BC%258F%25E7%25B7%25A8%25E7%25A8%258B& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&指令式编程&/a&走向&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/zh/%25E5%2587%25BD%25E6%%25E7%25A8%258B%25E5%25BC%258F%25E8%25AA%259E%25E8%25A8%2580& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&函数式编程&/a&。先来点咖啡,然后继续读下去。很快你对FP的理解就会让同事们刮目相看了。&/p&&p&什么是函数式编程(Functional Programming,FP)?它从何而来?可以吃吗?倘若它真的像那些鼓吹FP的人说的那么好,为什么实际应用中那么少见?为什么只有那些在读博士的家伙想要用它?而最重要的是,它母亲的怎么就那么难学?那些所谓的closure、continuation,currying,lazy evaluation还有no side effects都是什么东东(译者:本着保留专用术语的原则,此处及下文类似情形均不译)?如果没有那些大学教授的帮忙怎样把它应用到实际工程里去?为什么它和我们熟悉的万能而神圣的指令式编程那么的不一样?&br&我们很快就会解开这些谜团。刚才我说过实际工程和学术界之间的知识断层是有其历史原因的,那么就先让我来解释一下这个问题。答案,就在接下来的一次公园漫步中:&/p&公园漫步&p&时间机器启动……我们来到公元前380年,也就是2000多年前的雅典城外。这是一个阳光明媚的久违的春天,&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/zh/%25E6%259F%258F%25E6%258B%%259B%25BE& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&柏拉图&/a&和一个帅气的小男仆走在一片橄榄树荫下。他们正准备前往一个学院。天气很好,吃得很饱,渐渐的,两人的谈话转向了哲学。&/p&&p&“你看那两个学生,哪一个更高一些?”,柏拉图小心的选择用字,以便让这个问题更好的引导眼前的这个小男孩。&br&小男仆望向水池旁边的两个男生,“他们差不多一样高。”。&br&“‘差不多一样高’是什么意思?”柏拉图问。&br&“嗯……从这里看来他们是一样高的,但是如果走近一点我肯定能看出差别来。”&br&柏拉图笑了。他知道这个小孩已经朝他引导的方向走了。“这么说来你的意思是世界上没有什么东西是完全相同的咯?”&br&思考了一会,小男孩回答:“是的。万物之间都至少有一丁点差别,哪怕我们无法分辨出来。”&br&说到点子上了!“那你说,如果世界上没有什么东西是完全相等的,你怎么理解‘完全相等’这个概念?”&br&小男仆看起来很困惑。“这我就不知道了。”&/p&&p&这是人类第一次试图了解数学的本质。柏拉图认为我们所在的世界中,万事万物都是完美模型的一个近似。他同时意识到虽然我们不能感受到完美的模型,但这丝毫不会阻止我们了解完美模型的概念。柏拉图进而得出结论:完美的数学模型只存在于另外一个世界,而因为某种原因我们却可以通过联系着这两个世界的一个纽带来认识这些模型。一个简单的例子就是完美的圆形。没有人见过这样的一个圆,但是我们知道怎样的圆是完美的圆,而且可以用公式把它描述出来。&/p&&p&如此说来,什么是数学呢?为什么可以用数学法则来描述我们的这个宇宙?我们所处的这个世界中万事万物都可以用数学来描述吗?2 数理哲学是一门很复杂的学科。它和其他多数哲学一样,更着重于提出问题而不是给出答案。数学就像拼图一样,很多结论都是这样推导出来的:先是确立一些互不冲突的基础原理,以及一些操作这些原理的规则,然后就可以把这些原理以及规则拼凑起来形成新的更加复杂的规则或是定理了。数学家把这种方法称为“形式系统”或是“演算”。如果你想做的话,可以用形式系统描述俄罗斯方块这个游戏。而事实上,俄罗斯方块这个游戏的实现,只要它正确运行,就是一个形式系统。只不过它以一种不常见的形式表现出来罢了。&/p&&p&如果&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/wiki/%25E5%258D%258A%25E4%25BA%25BA%25E9%25A9%25AC%25E5%25BA%25A7%25CE%25B1& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&半人马阿尔法&/a&上有文明存在的话,那里的生物可能无法解读我们的俄罗斯方块形式系统甚至是简单的圆形的形式系统,因为它们感知世界的唯一器官可能只有鼻子(译者:偶的妈你咋知道?)也许它们是无法得知俄罗斯方块的形式系统了,但是它们很有可能知道圆形。它们的圆形我们可能没法解读,因为我们的鼻子没有它们那么灵敏(译者:那狗可以么?)可是只要越过形式系统的表示方式(比如通过使用“超级鼻子”之类的工具来感知这些用味道表示的形式系统,然后使用标准的解码技术把它们翻译成人类能理解的语言),那么任何有足够智力的文明都可以理解这些形式系统的本质。&br&有意思的是,哪怕宇宙中完全不存在任何文明,类似俄罗斯方块还有圆形这样的形式系统依旧是成立的:只不过没有智慧生物去发现它们而已。这个时候如果忽然一个文明诞生了,那么这些具有智慧的生物就很有可能发现各种各样的形式系统,并且用它们发现的系统去描述各种宇宙法则。不过它们可能不会发现俄罗斯方块这样的形式系统,因为在它们的世界里没有俄罗斯方块这种东西嘛。有很多像俄罗斯方块这样的形式系统是与客观世界无关的,比如说自然数,很难说所有的自然数都与客观世界有关,随便举一个超级大的数,这个数可能就和世界上任何事物无关,因为这个世界可能不是无穷大的。&/p&历史回眸3&p&再次启动时间机……这次到达的是20世纪30年代,离今天近了很多。无论&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/wiki/%25E6%%25E5%25A4%25A7%25E9%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&新&/a&&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/wiki/%25E8%E5%25A4%25A7%25E9%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&旧&/a&大陆,经济大萧条都造成了巨大的破坏。社会各阶层几乎每一个家庭都深受其害。只有极其少数的几个地方能让人们免于遭受穷困之苦。几乎没有人能够幸运的在这些避难所里度过危机,注意,我说的是几乎没有,还真的有这么些幸运儿,比如说当时普林斯顿大学的数学家们。&/p&&p&新建成的哥特式办公楼给普林斯顿大学带来一种天堂般的安全感。来自世界各地的逻辑学者应邀来到普林斯顿,他们将组建一个新的学部。正当大部分美国人还在为找不到一片面包做晚餐而发愁的时候,在普林斯顿却是这样一番景象:高高的天花板和木雕包覆的墙,每天品茶论道,漫步丛林。 一个名叫&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/zh/%25E9%2598%25BF%25E9%259A%%25BD%%25B7%25E9%%25E5%25A5%2587& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&阿隆佐·邱奇&/a&(Alonzo Church)的年轻数学家就过着这样优越的生活。阿隆佐本科毕业于普林斯顿后被留在研究院。他觉得这样的生活完全没有必要,于是他鲜少出现在那些数学茶会中也不喜欢到树林里散心。阿隆佐更喜欢独处:自己一个人的时候他的工作效率更高。尽管如此他还是和普林斯顿学者保持着联系,这些人当中有&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/zh/%25E8%2589%25BE%25E4%25BC%25A6%25C2%25B7%25E5%259B%25BE%25E7%& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&艾伦·图灵&/a&、&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/zh/%25E7%25BA%25A6%25E7%25BF%25B0%25C2%25B7%25E5%2586%25AF%25C2%25B7%25E8%25AF%25BA%25E4%25BC%258A%25E6%259B%25BC& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&约翰·冯·诺伊曼&/a&、&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/zh-hant/%25E5%25BA%%25B0%%%25C2%25B7%25E5%%25E5%25BE%25B7%25E5%25B0%2594& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&库尔特·哥德尔&/a&。&br&这四个人都对形式系统感兴趣。相对于现实世界,他们更关心如何解决抽象的数学问题。而他们的问题都有这么一个共同点:都在尝试解答关于计算的问题。诸如:如果有一台拥有无穷计算能力的超级机器,可以用来解决什么问题?它可以自动的解决这些问题吗?是不是还是有些问题解决不了,如果有的话,是为什么?如果这样的机器采用不同的设计,它们的计算能力相同吗?&br&在与这些人的合作下,阿隆佐设计了一个名为&a href=&//link.zhihu.com/?target=http%3A//zh.wikipedia.org/wiki/%25CE%259B%25E6%25BC%%25AE%2597& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&lambda演算&/a&的形式系统。这个系统实质上是为其中一个超级机器设计的编程语言。在这种语言里面,函数的参数是函数,返回值也是函数。这种函数用希腊字

我要回帖

更多关于 复合函数求导 的文章

 

随机推荐