【數盟致力于成為最卓越的數據科學(xué)社區,聚焦于大數據、分析挖掘、數據可視化領(lǐng)域,業(yè)務(wù)范圍:線(xiàn)下活動(dòng)、在線(xiàn)課程、獵頭服務(wù)、項目對接】
作者:abbey
為什么學(xué)習函數式編程
在閱讀DDD巨著(zhù)《Patterns, Principles, and Practices of Domain-Driven Design》的過(guò)程中,Scott在第5章提到了使用函數式編程語(yǔ)言配合貧血模型去實(shí)踐DDD的一種思路,這激發(fā)了我的無(wú)限遐想。
在軟件開(kāi)發(fā)領(lǐng)域,我們已經(jīng)擁有了許多的抽象方法論和大量的實(shí)現技術(shù)。但我個(gè)人認為,這一切歸根結底,都是人類(lèi)思維在軟件開(kāi)發(fā)領(lǐng)域的具體表達方式。而人類(lèi)在認識和分析軟件所要解決的業(yè)務(wù)領(lǐng)域問(wèn)題時(shí),思考的內容不外乎由兩個(gè)部分組成:『業(yè)務(wù)流程』與『業(yè)務(wù)規則』。前者,回答了業(yè)務(wù)活動(dòng)中先做什么后做什么的問(wèn)題;后者,則回答了遇到什么情況時(shí)應該怎么做的問(wèn)題。兩者結合后,得到我們需要的業(yè)務(wù)結果,或者叫作“實(shí)現業(yè)務(wù)目標”。
再想想目前學(xué)習和掌握的面向對象的一系列方法,又是如何將上述思維結果映射到軟件中去的呢?我認為是這樣的:
對于業(yè)務(wù)流程,我們將其表達為若干對象之間的合作,比如UML里序列圖的對象與消息,進(jìn)而具化為具體的類(lèi)及其職責,比如類(lèi)及其若干業(yè)務(wù)方法。
對于業(yè)務(wù)規則,我們將其表達為若干的判斷邏輯,比如UML流程圖里的判斷分支,進(jìn)而具化為業(yè)務(wù)方法里的if-else語(yǔ)句,或者再復雜一點(diǎn),表達為工廠(chǎng)、策略等設計模式的實(shí)際運用。
然后,我認為,對于復雜業(yè)務(wù)規則的梳理,可以象數學(xué)歸納法一樣進(jìn)行演繹:假設一個(gè)函數y=f(x),給定x的定義域,確定y的值域。特別是在排列組合等方面的一些問(wèn)題,也經(jīng)常采用遞歸的方式來(lái)解決。所以,從這個(gè)角度講,函數式編程更貼近人類(lèi)思維習慣,所以讓我自然而然地把目光轉向了它。
在選擇具體的函數式編程語(yǔ)言時(shí),我首先想到的是它最好是同時(shí)能支持面向對象編程的。因為即便LISP作為函數式編程語(yǔ)言的先祖,誕生已長(cháng)達半個(gè)世紀,但單純的函數式編程語(yǔ)言與面向對象編程語(yǔ)言相比,在抽象領(lǐng)域概念、組合系統模塊、實(shí)現信息隱蔽等方面存在一定的差距,所以一直沒(méi)有成為軟件開(kāi)發(fā)的主流。
信息隱蔽原理:在西安電子科大蔡希堯與陳平老師于1993年合作出版的《面向對象技術(shù)》一書(shū)中是這樣描述的:把需求和求解的方法分離;把相關(guān)信息——數據結構和算法,集中在一個(gè)模塊之中,和其他模塊隔離,它們不能隨便訪(fǎng)問(wèn)這個(gè)模塊內部的信息。
其次,由于我的語(yǔ)言路線(xiàn)是從Pascal → C → C → C#,所以我希望能選擇一種風(fēng)格近似于C、強類(lèi)型的函數式編程語(yǔ)言。在比較了F#、R、ErLang等幾種常見(jiàn)的函數式編程語(yǔ)言之后,我最終選擇了Scala。
注:以下內容,節選翻譯或參考自《Programming in Scala》第1章、第3章,《Programming Scala》第6章,不算完整意義上的個(gè)人心得。
純的函數是沒(méi)有副作用的。無(wú)論何時(shí)何地,對于一個(gè)函數y=f(x),給定x必定得到y,不會(huì )因此產(chǎn)生二義結果。因此無(wú)論對于代碼測試還是并發(fā),由于給定輸入必定得到預期輸出,而不受其他因素干擾,所以能有效減少Bug產(chǎn)生。
在函數式編程里,大量使用immutable的值。這意味著(zhù)函數運算的結果總會(huì )創(chuàng )建一個(gè)新的實(shí)例,避免了通常并發(fā)環(huán)境下為防止數據共享沖突而采取的保護機制。盡管這需要額外的Copy操作,但Scala針對性地提供了高效的Copy實(shí)現,以及延遲計算等彌補機制。
函數是一等公民。函數作為表達式的一部分,可以借由函數之間的嵌套、組合,實(shí)現復雜的判斷邏輯。
Scala是面向對象與函數式編程的混合語(yǔ)言,所以能有效結合二者的優(yōu)點(diǎn)。
Scala屬于Java生態(tài)圈,可以在JVM平臺上與Java一起編譯運行,所以許多Java的框架、工具都可以直接應用于Scala語(yǔ)言編寫(xiě)的項目。
Scala視一切數據類(lèi)型皆為對象,且支持閉包、lambda、by-name參數等特性,語(yǔ)法簡(jiǎn)潔明快。
Scala使用Actor作為并發(fā)模型,與Akka框架自然契合。這是一種區別于傳統的、基于數據共享、以鎖為主要機制的并發(fā)模型,其特點(diǎn)在于以Actor為基本單位、沒(méi)有數據共享、基于消息傳遞實(shí)現Actor之間的協(xié)作,因此可以有效避免死鎖、減少競爭。
最后,如果有朝一日要轉向大數據領(lǐng)域,有Spark這樣的大型框架作為支撐。知乎:與 Hadoop 對比,如何看待 Spark 技術(shù)?
說(shuō)了那么多,我的根本目的還是要將Scala作為實(shí)現DDD的主要武器。那么試想一下,Scala在我們實(shí)現DDD的過(guò)程中能有哪些幫助呢?我暫且胡侃亂謅如下:
表示值對象、領(lǐng)域事件等元素更直觀(guān)。值對象、領(lǐng)域事件在DDD里都應該是immutable的,以往多采取POCO形式表示,現在改用Scala里的val以及case class表示,在語(yǔ)法層面就直觀(guān)地表明是不可修改的。
在類(lèi)的方法層面實(shí)現CQRS時(shí)有語(yǔ)法支持。用Scala里的Function(返回類(lèi)型為非Unit)對應CQRS里的Query,保證類(lèi)的方法沒(méi)有副作用;用Procedure(返回類(lèi)型為Unit)對應CQRS里的Command,明確表明這一類(lèi)方法會(huì )產(chǎn)生修改狀態(tài)等副作用。這同樣從語(yǔ)法層面就能對二者進(jìn)行明確區分。
模式匹配豐富了函數操作。除了正則表達式,Scala形式多樣的模式匹配語(yǔ)法,為提取數據、完成數據分組聚合等運算、實(shí)現邏輯判斷提供了強大支持。比如定義def sum_count(ints:Seq[Int) = (ints.sum, ints.size)這樣一個(gè)函數后,我們可以這樣調用,以得到一個(gè)1至6的整數序列的整數值合計,及該序列的尺寸: val(sum, count) = sum_count(List(1, 2, 3, 4, 5, 6))。
為實(shí)現DSL提供有力支持。Scala自帶有解析框架,加上靈活的函數語(yǔ)法支持,要自己實(shí)現一套DSL及其相應的語(yǔ)法解析器將不再困難。比如在配置文件里這樣的一條配置語(yǔ)句,表示退休條件為年齡達到60周歲或者工齡屆滿(mǎn)30年:retire = (Age >= 60) || (ServiceLength >= 30)。以往的方式是自己寫(xiě)一個(gè)語(yǔ)法解析器,把這條文本轉換成相應的Specification對象,然后扔給聚合去使用?,F在有了Scala的幫助,就使編寫(xiě)語(yǔ)法解析器這一環(huán)節的工作量大大減少。
合理的高階函數設計,使規則編寫(xiě)得到簡(jiǎn)化。比如打折規則、費用報銷(xiāo)規則,以往可能需要若干層的if-else嵌套,現在則將通過(guò)高階函數得到大幅簡(jiǎn)化。對此,我強烈推薦劉光聰先生的視頻Refactoring to Functions,你會(huì )在劉先生的重構過(guò)程中發(fā)現高階函數的強大。
Actor為高效并發(fā)打下基礎。Actor內部完全自治,自帶用于存儲消息的mailbox,與其他Actor只能通過(guò)消息進(jìn)行交互,每個(gè)Actor都是并發(fā)的一個(gè)基本單位。這些特點(diǎn),非常適合于采取Event Sourcing方式實(shí)現的DDD。每個(gè)聚合都好比一個(gè)Actor,在聚合內部始終保持數據的強一致性,而在聚合之間交互的領(lǐng)域事件則好比Actor之間的消息,聚合之間借由領(lǐng)域事件和Saga保證數據的最終一致性。
Trait成為AOP利器。Trait是Scala的另一大特色,它就象AOP織入一樣,能動(dòng)態(tài)地給某個(gè)類(lèi)型注入方法或者結構。比如配合類(lèi)Circuit和with后面那4個(gè)Trait的定義,val circuit = new Circuit with Adders with Multiplexers with Flipflops with MultiCoreProcessors這樣就創(chuàng )建了一個(gè)帶有加法器、乘法器、觸發(fā)器和多核處理器的元件。
隱式實(shí)現為類(lèi)型擴展提供支持。對應C#里的靜態(tài)擴展方法,Scala通過(guò)implicit為實(shí)現數據類(lèi)型的方法擴展提供了便捷,成為T(mén)rait之外的另一個(gè)功能擴展手段。
能降低常見(jiàn)BDD框架的學(xué)習成本。盡管這一點(diǎn)可能比較牽強,但我正在努力摸索如何將BDD與DDD結合,而常見(jiàn)的Cucumber、Spock等一些BDD框架,其語(yǔ)法與Scala比較相近,所以我才有如此一說(shuō)。
以下是我目前主要的學(xué)習資料,并衷心歡迎各位留言補充。
Programming in Scala:由Scala語(yǔ)言的設計師Martin Odersky編寫(xiě),循序漸進(jìn),配合了大量實(shí)例,入門(mén)必讀吧。
Programming Scala:視角與上面那本有點(diǎn)不一樣,沒(méi)有Scala語(yǔ)言基礎會(huì )感覺(jué)很困難,適合掌握了基本語(yǔ)法后溫故而知新。
Scala 官方文檔:Scala的官網(wǎng),作為尋找資料的出發(fā)點(diǎn)是不錯的。
Scala 課堂:中文版的Scala基本語(yǔ)法在線(xiàn)課堂。
Scala Synatax Primer:由Jim McBeath整理的Scala語(yǔ)法概要,可以當字典用。
The Neophyte’s Guide to Scala:很出名的一個(gè)Scala入門(mén)指南,以Scala中的提取器Extractor作為實(shí)例開(kāi)始。
Scala 初學(xué)指南:這是上面那本指南的中譯本。
Effective Scala:中文版的Scala高效編程
SBT中文入門(mén)指南:Scala Build Tool
Scala 中文社區:不算活躍,原因你懂的。
Scala User:Scala入門(mén)者聚集地,沒(méi)有Stack Overflow那么嚴格,但也需要點(diǎn)爬墻的身手。
Java SE:先裝這個(gè)
Scala SDK:再裝這個(gè)
SBT:然后裝這個(gè)
IntelliJ IDEA:最后裝這個(gè),就能比較方便地開(kāi)始Scala編程了
最近讀的書(shū)很多也很雜,DDD、BDD、Scala、Cucumber以及Java基礎等等都有涉及,真恨不得一口吃成個(gè)大胖子。由于時(shí)間和精力有限,所以現在知識消化很成問(wèn)題,遲遲沒(méi)有進(jìn)入學(xué)以致用的環(huán)節,只能先這樣紙上談兵了,好歹先把自己在學(xué)習過(guò)程中的一些思考、看到的好東西先記載下來(lái),以備將來(lái)之需。
原文鏈接:https://codingstyle.cn/topics/134?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
聯(lián)系客服