函數(shù)式編程心得

函數(shù)式編程心得

最近一年來筛欢,我在函數(shù)式編程上不停探索浸锨,想要構(gòu)建一個屬于自己的編程思維。然而在實際開發(fā)過程中版姑,經(jīng)常會因為各種原因?qū)е滤悸坊靵y柱搜,難以繼續(xù)開發(fā)。
所以這篇文章主要總結(jié)一下自己的開發(fā)思維模式剥险,以便自己開發(fā)過程中能有經(jīng)驗所依聪蘸。

當需要開發(fā)一個系統(tǒng)時,需要明確一件事情表制,即任何計算機程序健爬,都可以理解為解釋器。

eval和apply的矛盾統(tǒng)一

既然涉及到解釋器么介,那么免不了談?wù)揺val和apply兩個函數(shù)娜遵。
eval函數(shù)作用是求值表達式,apply函數(shù)是使用指定參數(shù)去調(diào)用某個函數(shù)壤短。如下所示

(eval '(+ 1 1)')

(apply + (list 1 1))

任何程序都是這兩個函數(shù)的循環(huán)遞歸調(diào)用设拟。就如SICP里面的求值器慨仿,eval求值一個表達式的時候,如果該表達式是程序纳胧,就將函數(shù)名和參數(shù)傳遞個apply镰吆,求出結(jié)果并返回。如下所示:

(define (eval exp env)
  (cond ...
        [(application? exp)
         (apply (eval (operator exp) env)
                (list-of-values (operands exp) env))]
        [else
         (error 'eval "Unknow expression type -- EVAL" exp)]))

(define (apply procedure arguments)
  (cond [(primitive-procedure? procedure)
         (apply-primitive-procedure procedure arguments)]
        [(compound-procedure? procedure)
         (eval-sequence
          (procedure-body procedure)
          (extend-environment
           (procedure-parameters procedure)
           arguments
           (procedure-environment procedure)))]
        [else
         (error 'apply "Unknow procedure type -- APPLY" procedure)]))

eval需要調(diào)用apply躲雅,而apply也需要調(diào)用eval來求值子表達式。這就稱為元循環(huán)骡和。

而eval和apply從本質(zhì)上看是同一個東西相赁,它們都是在指定環(huán)境下對表達式進行求值,看看下面它們的入?yún)ⅲ?/p>

(define (eval expr env))
(define (apply proc args))

對于eval來說慰于,它的表達式類型很多钮科,每個表達式都必須在環(huán)境下對其求值。而apply需要函數(shù)作為參數(shù)婆赠,其實這作為參數(shù)的函數(shù)也可以被認為是表達式绵脯,而該作為參數(shù)的函數(shù)所需的信息,也是可以被認為是環(huán)境休里。不同的地方僅僅在于eval的環(huán)境比較單一蛆挫,而apply則豐富多樣。對應(yīng)地妙黍,eval的表達式非常豐富悴侵,而apply的表達式單一。

所以它們兩個都是解釋器拭嫁,都是在特定環(huán)境下對表達式進行解釋可免。

計算機程序是解釋器

理解了這一個層面,就能快速理解復(fù)雜系統(tǒng)背后的精髓做粤。比如浇借,大家熟悉的MYSQL看似復(fù)雜精深,其實它對我們而言只是一個解釋器怕品,它解釋我們編寫的SQL語言妇垢,并把解釋結(jié)果返回給我們。這個解釋器的環(huán)境包含著我們所說的表肉康,數(shù)據(jù)修己,索引等等。而對SQL解釋的過程迎罗,就是不斷的對環(huán)境中的各種數(shù)據(jù)進行查詢和修改睬愤。

又比如,我們?nèi)粘K鶎懙腤eb系統(tǒng)纹安,也不過是另一種解釋器而已尤辱。該解釋器的表達式就是用戶發(fā)送過來的http請求砂豌,而求值環(huán)境就是數(shù)據(jù)庫,緩存等等光督。對表達式的求值程序阳距,被我們拆分成路由,控制器结借,具體業(yè)務(wù)邏輯等等層面而已筐摘。

把所有程序當作是解釋器,可以擴寬我們的編程思維船老。而一旦我們想要運用這個編程思維去設(shè)計程序的時候咖熟,我們就必須深刻理解解釋器背后設(shè)計思維和概念。

比如柳畔,分析一個模塊時馍管,我們可以思考該模塊到底是什么樣的解釋器,這個解釋器的表達式有哪些薪韩,分別有哪些作用确沸,解釋器的求值環(huán)境到底是什么?

舉個具體例子俘陷,現(xiàn)在需要做一個http服務(wù)器罗捎,其功能是從socket中讀取用戶發(fā)送的字節(jié)流,并轉(zhuǎn)換成json格式拉盾。

按照過去的思維去思考宛逗,功能無非就是寫一個函數(shù),其入?yún)⑹莝ocket字節(jié)流盾剩,返回是json格式雷激。然后就開始編寫這個函數(shù)的邏輯:不斷的讀取input的內(nèi)容,對這些內(nèi)容進行解析告私,而后把解析內(nèi)容組合起來屎暇,形成json格式,并返回驻粟。

用解釋器的設(shè)計思維根悼,同樣需要寫這個函數(shù),只不過這個函數(shù)的實現(xiàn)方式更加細致靈活蜀撑。這種實現(xiàn)方式挤巡,不再簡單地把socket字節(jié)流當成是參數(shù),而是把字節(jié)流當做是求值環(huán)境酷麦,而我們需要做的是構(gòu)建一些合理的表達式矿卑,在特定環(huán)境下,當用自己編寫的解釋器去求值表達式沃饶,從而將socket字節(jié)流轉(zhuǎn)換成json格式母廷。

表達式設(shè)計思維

運用解釋器思維來設(shè)計系統(tǒng)的過程中轻黑,第一步要做的事情,也是最難的事情就是設(shè)計表達式琴昆,設(shè)計良好的表達式能夠讓上層程序員高效開發(fā)應(yīng)用氓鄙。這往往有三個重要問題需要思考:基本表達式是什么,如何組合基本表達式业舍,如何構(gòu)建它們的抽象等抖拦。

下面,我將試著闡述這三個問題舷暮。
首先要明確基本表達式的通用定義态罪,我給它定義為用戶操作環(huán)境的基本方式,它屏蔽環(huán)境的細節(jié)脚牍,使得用戶可以更流程表達想法向臀。換句話說只要是能夠操作環(huán)境巢墅,并且能讓用戶無需關(guān)注環(huán)境的诸狭,都算是基本表達式。

然而基礎(chǔ)表達式的功能是很單一的君纫,我們需要對基礎(chǔ)表達式進行各種形式的組合驯遇,才能完整的表達一件事物。因此蓄髓,如何對基本元素進行組合叉庐,是一件很必要的事情。組合形式一般而已無關(guān)緊要会喝,最關(guān)鍵的在于組合表達式的意義陡叠。有些組合表達式,將順序求值子表達式肢执,并將子表達式的結(jié)果匯總起來枉阵,有些組合表達式卻是組成一條調(diào)用鏈,后表達式依賴前表達式的結(jié)果预茄。

抽象應(yīng)該如何理解呢兴溜?舉例來說,掀開被子耻陕,坐立起來拙徽,穿好鞋子,這些動作我們可以匯總起來叫做起床诗宣,而起床就是這些動作的抽象膘怕。只要我對自己說起床,我的身體就做那些動作召庞,就叫做使用抽象淳蔼。在構(gòu)建表達式的時候侧蘸,我們?yōu)榱烁鲿车乇磉_自己的想法,同樣需要一些方式進行抽象和使用抽象鹉梨。

舉例說讳癌,define, +, lambda, 數(shù)字,變量都是scheme的基本表達式存皂,這些基礎(chǔ)表達式的意義都依賴環(huán)境晌坤,甚至define表達式還會改變環(huán)境,然而基礎(chǔ)表達式很好屏蔽了環(huán)境旦袋,使得用戶無需了解環(huán)境的任何細節(jié)骤菠。
(define a (lambda () (+ 1 1) (+ 2 2))) 這是一種組合方式,即通過list將基本表達式組合起來疤孕,形成功能更加強大的表達式商乎。

(define a (lambda () (+ 1 1) (+ 2 2))) 這是一種組合方式,即通過list將基本表達式組合起來祭阀,形成功能更加強大的表達式鹉戚。
而lambda表達式是一種抽象方式,它將多個表達式組裝形成一個匿名函數(shù)专控。調(diào)用該匿名函數(shù)抹凳,即相當于調(diào)用lambda體里的組合表達式。由于define過程把 a 與一個lambda關(guān)聯(lián)起來伦腐,也就是說 a 可以說是lambda的抽象赢底,只要調(diào)用a,就相當于調(diào)用lambda里的過程柏蘑。換句話說幸冻,抽象是通過lambda 來封裝,define建立關(guān)聯(lián)咳焚。

由于我們要描述一些表達式洽损,可以采用下面這種數(shù)學標準方法來描述需要構(gòu)建的表達式。

image.png

此外黔攒,使用編譯器思維去設(shè)計程序趁啸,是要達成最終目的的,因此也需要以目標導(dǎo)向原則來設(shè)計表達式督惰。例如要將字節(jié)流轉(zhuǎn)換為json格式不傅。轉(zhuǎn)換過程中,需要從字節(jié)流轉(zhuǎn)換成字符串赏胚,再提取里面的內(nèi)容访娶,組合成json。因此觉阅,這里涉及到轉(zhuǎn)換表達式崖疤,提取表達式秘车,組合表達式。

解釋器設(shè)計心得

我沒有寫過復(fù)雜的編譯器劫哼,所以在設(shè)計編譯器的方面有一定欠缺叮趴,這里總結(jié)出來的心得或許不夠成熟。
表達式可以采用面向?qū)ο笏季S進行封裝权烧,這是做模塊化最基本的單元眯亦。而在scheme中,對象是可以使用算子來模擬的般码,而且算子還具備其他語言外更靈活的特性妻率。
一個復(fù)雜的編譯器,往往由多個子編譯器組合而成板祝。不要試圖用一個編譯器去囊括所有功能宫静,而是應(yīng)該拆分成職責明確的子編譯器。對于功能單一的模塊券时,能用簡單函數(shù)取代就不要用編譯器理念來復(fù)雜化模塊孤里。

復(fù)雜編譯器有多個子編譯器組成,而子編譯器也有更細小更具體的編譯器所組成革为。我們是否能將把編譯器也看成是表達式扭粱,子編譯器的排列組合舵鳞,就像一個復(fù)合表達式包含多個子表達式震檩。因此,是否可以試圖夠構(gòu)造一個更高層次的編譯器蜓堕,讓編譯器去生成編譯器抛虏?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市套才,隨后出現(xiàn)的幾起案子迂猴,更是在濱河造成了極大的恐慌,老刑警劉巖背伴,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件沸毁,死亡現(xiàn)場離奇詭異,居然都是意外死亡傻寂,警方通過查閱死者的電腦和手機息尺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來疾掰,“玉大人搂誉,你說我怎么就攤上這事【裁剩” “怎么了炭懊?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵并级,是天一觀的道長。 經(jīng)常有香客問我侮腹,道長嘲碧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任父阻,我火速辦了婚禮呀潭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘至非。我一直安慰自己钠署,他們只是感情好,可當我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布荒椭。 她就那樣靜靜地躺著谐鼎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪趣惠。 梳的紋絲不亂的頭發(fā)上狸棍,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音味悄,去河邊找鬼草戈。 笑死,一個胖子當著我的面吹牛侍瑟,可吹牛的內(nèi)容都是我干的唐片。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼涨颜,長吁一口氣:“原來是場噩夢啊……” “哼费韭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起庭瑰,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤星持,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后弹灭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體督暂,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年穷吮,在試婚紗的時候發(fā)現(xiàn)自己被綠了逻翁。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡酒来,死狀恐怖卢未,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤辽社,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布伟墙,位于F島的核電站,受9級特大地震影響滴铅,放射性物質(zhì)發(fā)生泄漏戳葵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一汉匙、第九天 我趴在偏房一處隱蔽的房頂上張望拱烁。 院中可真熱鬧,春花似錦噩翠、人聲如沸戏自。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽擅笔。三九已至,卻和暖如春屯援,著一層夾襖步出監(jiān)牢的瞬間猛们,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工狞洋, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留弯淘,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓吉懊,卻偏偏與公主長得像庐橙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子惕它,可洞房花燭夜當晚...
    茶點故事閱讀 42,802評論 2 345