用Ruby的Lambda實(shí)現(xiàn)基本數(shù)值運(yùn)算

之前有發(fā)布一篇文章《用Ruby簡(jiǎn)單模擬Lambda演算》討論了如何用Ruby的Lambda來(lái)表達(dá)0, 1焙糟, 2這些整數(shù)口渔。專業(yè)上稱他們?yōu)?strong>邱奇數(shù),順手貼一段Wiki穿撮。

邱奇編碼是把數(shù)據(jù)和運(yùn)算符嵌入到Lambda演算內(nèi)的一種方式缺脉,最常見的形式是邱奇數(shù),它是使用Lambda符號(hào)的自然數(shù)的表示法混巧。這種方法得名于阿隆佐·邱奇枪向,他首先以這種方法把數(shù)據(jù)編碼到Lambda演算中勤揩。

Calculator

但如果要構(gòu)建一個(gè)程序咧党,僅僅有這些基本的數(shù)據(jù)類型是不夠的。很多時(shí)候我們會(huì)想對(duì)我們生成的這些數(shù)字做些猥瑣的操作陨亡。比如我們常用的加法傍衡,減法,乘法负蠕,以及階乘等等蛙埂。下面我們就來(lái)實(shí)現(xiàn)這些操作。

0. 熱身

古人云:“溫故而知新遮糖,可以為師矣”绣的。那么開始實(shí)現(xiàn)基本數(shù)值運(yùn)算之前我們先來(lái)溫習(xí)一下之前的知識(shí),順手實(shí)現(xiàn)幾個(gè)邱奇數(shù)

ZERO = -> p { -> x { x } }
ONE = -> p { -> x { p[x] } }

然后寫一個(gè)函數(shù)把對(duì)應(yīng)的邱奇數(shù)以我們熟悉的方式展示出來(lái)

def to_integer(n)
  n[-> x {x + 1}][0]
end
>> to_integer(ONE)
 => 1
>> to_integer(ZERO)
 => 0

更多的邱奇數(shù)

TWO = -> p { -> x { p[p[x]] } }
THREE = -> p { -> x { p[p[p[x]]] } }
FOUR = -> p { -> x { p[p[p[p[x]]]] } }

# ....

只要反復(fù)調(diào)用p我們便能夠構(gòu)建更大的邱奇數(shù)欲账,那么我們是否能夠創(chuàng)建一個(gè)函數(shù)來(lái)完成這個(gè)反復(fù)調(diào)用p的過(guò)程呢屡江?我期待的函數(shù)是這樣工作的

F(ONE) => TWO
F(TWO) => THREE
F(THREE) => FOUR

似乎有點(diǎn)意思,那這里我姑且命名這個(gè)函數(shù)為SUCC赛不。

SUCC = -> n {
  -> p {
    -> x {
      p[n[p][x]]
    }
  }
}

講解: 簡(jiǎn)單地可以把這個(gè)函數(shù)拆開來(lái)看惩嘉,我們?cè)?code>x的基礎(chǔ)上調(diào)用np就會(huì)得到我們期待的數(shù)字n。當(dāng)我們?cè)诘玫浇Y(jié)果的基礎(chǔ)上再執(zhí)行一次p的時(shí)候就會(huì)得到n + 1了踢故。

>> to_integer(SUCC[ZERO])
 => 1
>> to_integer(SUCC[ONE])
 => 2

OK文黎,結(jié)果正是我們所期待的惹苗。 但好像熱身熱過(guò)頭了,那我們馬上開始用Lambda實(shí)現(xiàn)基本數(shù)值運(yùn)算耸峭。

1. 加法

熱身里面好像隱藏了一些東西桩蓉,我們平時(shí)所定義的一個(gè)數(shù)字,其本質(zhì)就是在0的基礎(chǔ)上執(zhí)行 加一操作得到的劳闹,我們想要什么數(shù)字就執(zhí)行多少次加一操作触机,而這個(gè)時(shí)候我們的起始值是0。那么如果起始值不是0呢玷或?那會(huì)發(fā)生什么事情?

這不就是加法的本質(zhì)嗎儡首?我們把兩個(gè)數(shù)m,n相加偏友, 其實(shí)就相當(dāng)于在m的基礎(chǔ)上做了n加一操作罷了蔬胯。只是這個(gè)過(guò)程太平常了,平常到我們幾乎意識(shí)不到它的存在位他。

那現(xiàn)在就要把代碼寫下來(lái)了氛濒, 可以復(fù)用我們熱身的時(shí)候定義的SUCC函數(shù),寫下如下代碼

ADD = -> n {
  -> m {
   n[SUCC][m]
  }
}

講解: 我們用到了我們已經(jīng)實(shí)現(xiàn)的SUCC函數(shù)鹅髓,從字面上來(lái)理解就是我們從基礎(chǔ)數(shù)m開始舞竿,不斷遞增自身,當(dāng)遞增n次的時(shí)候我們就能夠得到n + m的結(jié)果了窿冯。如果拆開來(lái)看會(huì)更加明顯骗奖,我們當(dāng)nTHREEmTWO的時(shí)候醒串,代入函數(shù)执桌,便可得到這樣一個(gè)表達(dá)式SUCC[SUCC[SUCC[TWO]]],其實(shí)就是把TWO遞增3次芜赌,最后會(huì)得到FIVE仰挣。

驗(yàn)證一下結(jié)果是否正確

>> to_integer(ADD[FOUR][FIVE])
9

>> to_integer(ADD[FIVE][ONE])
6

結(jié)果還是另人滿意的。

2. 乘法

實(shí)現(xiàn)了加法之后缠沈,似乎乘法已經(jīng)離我們不遠(yuǎn)了膘壶。我們回想一下最初學(xué)習(xí)乘法的時(shí)候,小學(xué)老師肯定不是一上來(lái)就講乘法的洲愤。一般他們都會(huì)列出這樣一條式子m + m + m + m + .....颓芭,就是一個(gè)數(shù)m加上自身n次,這就是乘法的本質(zhì)了禽篱。然后他們會(huì)把這個(gè)式子表示成m x n畜伐。那既然我們已經(jīng)實(shí)現(xiàn)了加法了,是否可以利用我們已經(jīng)實(shí)現(xiàn)的ADD函數(shù)來(lái)實(shí)現(xiàn)乘法呢?

按照乘法的原理來(lái)實(shí)現(xiàn)MULTI函數(shù)

MULTI = -> n {
  -> m {
    n[ADD[m]][ZERO]
  }
}

講解: 從字面上來(lái)理解就是我們要從基礎(chǔ)數(shù)ZERO開始躺率,累加m這個(gè)數(shù)n次玛界。拆開來(lái)看會(huì)更加直觀万矾,我們假設(shè)mTWOnTHREE的時(shí)候慎框,會(huì)轉(zhuǎn)化為ADD[TWO][ADD[TWO][ADD[TWO][ZERO]]]良狈。

檢驗(yàn)一下結(jié)果

>>  p to_integer(MULTI[FOUR][FIVE])
20 

>>  p to_integer(MULTI[ONE][FIVE])
5

3. 減法

加法和乘法似乎都不會(huì)很難,但是減法就有點(diǎn)麻煩了笨枯。我們前面都感受到加法的本質(zhì)就是一個(gè)不斷重復(fù)調(diào)用指定函數(shù)的過(guò)程薪丁。但減法應(yīng)該是一個(gè)同他相反的過(guò)程,這該怎么做跋诰严嗜?調(diào)用方法我們還知道該怎么調(diào),但是我們沒有辦法把已經(jīng)調(diào)用了的方法回退洲敢,讓它返回前一次調(diào)用的結(jié)果漫玄。因此,這個(gè)作者才會(huì)說(shuō)Lambda Calculus: Subtraction is hard压彭。咋整睦优?下面先介紹一下我們需要用到的輔助函數(shù)。

1) 有序?qū)?/h3>

Wiki上面對(duì)有序?qū)?/a>的解釋

在數(shù)學(xué)中壮不,有序?qū)?/strong>是兩個(gè)對(duì)象的搜集汗盘,使得可以區(qū)分出其中一個(gè)是“第一個(gè)元素”而另一個(gè)是“第二個(gè)元素”(第一個(gè)元素和第二個(gè)元素也叫做左投影右投影)。帶有第一個(gè)元素a和第二個(gè)元素b的有序?qū)νǔ憺?a, b)询一。

我們嘗試用Lambda表達(dá)式來(lái)實(shí)現(xiàn)這個(gè)有序?qū)?/p>

PAIR = -> first {
  -> second {
    -> p {
      p[first][second]
    }
  }
}

LEFT -> first {
  -> second {
    first
  }
}

RIGHT -> first {
  -> second {
    second
  }
}

我們定制了一個(gè)PAIR隐孽,它接受3個(gè)參數(shù),分別是兩個(gè)邱奇數(shù)以及一個(gè)訪問(wèn)函數(shù)家凯,訪問(wèn)函數(shù)就是我們的LEFT, RIGHT分別用來(lái)提取PAIR中的兩個(gè)邱奇數(shù)缓醋。驗(yàn)證一下這個(gè)PAIR是否能夠像我期望那樣去運(yùn)行?

>> p to_integer(PAIR[TWO][FIVE][LEFT])
2

>> p to_integer(PAIR[TWO][FIVE][RIGHT])
5

工作正常。接下來(lái)我們看看滑動(dòng)窗口绊诲。

2) 滑動(dòng)窗口

計(jì)算機(jī)網(wǎng)絡(luò)里面有過(guò)這樣一個(gè)概念。形式上理解就是

[ 1 2 ] 3 4 5 6
1 [ 2 3 ] 4 5 6
1 2 [ 3 4 ] 5 6
1 2 3 [ 4 5 ] 6
1 2 3 4 [ 5 6 ]

這能看出什么褪贵?在中括號(hào)括起來(lái)的地方不就是我們有序?qū)岬嘀可厦孢@個(gè)滑動(dòng)窗口,其實(shí)我們可以用有序?qū)?lái)模擬脆丁。當(dāng)初始值設(shè)置為(0, 0)的時(shí)候滑動(dòng)3次就會(huì)有如下效果

[0, 0]
[0, 1]
[1, 2]
[2, 3]

有點(diǎn)像是我們之前定義的SUCC方法世舰,只是我們那時(shí)候只操作一個(gè)邱奇數(shù),返回一個(gè)邱奇數(shù)槽卫。而在這里我們是傳入一個(gè)PAIR, 然后返回這個(gè)PAIR向右滑動(dòng)的結(jié)果跟压。

那我們嘗試編寫這個(gè)函數(shù)

SLIDE = -> p {
  PAIR[p[RIGHT]][SUCC[p[RIGHT]]]
}

講解: 該函數(shù)接受一個(gè)PAIR作為參數(shù),然后創(chuàng)建一個(gè)新的PAIR歼培,以原來(lái)PAIR的右邊的元素作為新PAIR的左邊的元素震蒋。而新PAIR右邊的元素用的是原來(lái)PAIR右邊元素遞增之后的值茸塞。

在驗(yàn)證這個(gè)方法正確性之前我還需要實(shí)現(xiàn)to_pair這個(gè)方法,用來(lái)方便我們對(duì)PAIR內(nèi)容的檢查

def to_pair(pair)
  "(#{to_integer(pair[LEFT])}, #{to_integer(pair[RIGHT])})"
end
>> puts to_pair(PAIR[ONE][TWO])
(1, 2)

>> puts to_pair(SLIDE[PAIR[ONE][TWO]])
(2, 3)

>> puts to_pair(SLIDE[SLIDE[PAIR[ONE][TWO]]])
(3, 4)

哈哈查剖,SLIDE的行為已經(jīng)跟我們的預(yù)期一樣了钾虐。

3) 實(shí)現(xiàn)減法

說(shuō)了一大堆,并且實(shí)現(xiàn)了PAIR, SLIDE笋庄。但是我們還是沒有解決問(wèn)題效扫。我們通過(guò)PAIR以及SLIDE的配合可以獲取到當(dāng)前的邱奇數(shù)還有它的前驅(qū)邱奇數(shù)。那么接下來(lái)怎么搞直砂?我們換個(gè)角度想想這個(gè)問(wèn)題菌仁,我們現(xiàn)在已經(jīng)能夠構(gòu)造一個(gè)PAIR以及SLIDE了,那么我們是否可以實(shí)現(xiàn)一個(gè)函數(shù)静暂,讓它能夠直接返回我們傳入某個(gè)邱奇數(shù) 的前一個(gè)邱奇數(shù)掘托?如果我們能夠?qū)崿F(xiàn)這樣一個(gè)函數(shù)PRED,那么當(dāng)我對(duì)一個(gè)數(shù)m重復(fù)調(diào)用PRED這個(gè)函數(shù)n次的時(shí)候籍嘹,不就可以獲取到m - n的值了嗎闪盔?(當(dāng)然我們的前提是 m >= n畢竟我們現(xiàn)在還沒有考慮到負(fù)數(shù))

有了前面的基礎(chǔ)PRED方法似乎比較好實(shí)現(xiàn)

PRED = -> n {
  n[SLIDE][PAIR[ZERO][ZERO]][LEFT]
}

講解: 當(dāng)我們傳入的邱奇數(shù)n的時(shí)候,我們對(duì)最基本的有序?qū)?code>(0, 0)向右滑動(dòng)n次辱士,然后就能得到新的有序?qū)?code>(n - 1, n)泪掀,我們?nèi)∮行驅(qū)ψ筮呑鳛榻Y(jié)果就能夠得到 n-1

檢驗(yàn)一下PRED的執(zhí)行情況

>> p to_integer(PRED[FOUR])
3

>> p to_integer(PRED[TWO])
1

>> p to_integer(PRED[ONE])
0

Cool,已經(jīng)可以獲取到指定邱奇數(shù)的前一個(gè)邱奇數(shù)了颂碘。接下來(lái)干嘛呢异赫?好像減法的實(shí)現(xiàn)已經(jīng)呼之欲出了。其實(shí)我們實(shí)現(xiàn)的PRED就是一個(gè)減一操作头岔,如果我們想要得到m - n的結(jié)果塔拳,那么只需要對(duì)m執(zhí)行減一操作n次即可達(dá)到目的。

SUBT = -> m {
  -> n {
    n[PRED][m]
  }
}

檢驗(yàn)結(jié)果

>> p to_integer(SUBT[FIVE][ONE])
4

>> p to_integer(SUBT[FIVE][TWO])
3

>> p to_integer(SUBT[THREE][TWO]
1

我去峡竣,總算大功告成靠抑。相比起加法還有乘法,要用Lambda來(lái)實(shí)現(xiàn)減法居然要耗費(fèi)這么多力氣适掰。最后簡(jiǎn)單總結(jié)一下減法的思路: 首先我們需要設(shè)計(jì)一種數(shù)據(jù)結(jié)構(gòu)PAIR用來(lái)存儲(chǔ)兩個(gè)邱奇數(shù)颂碧。然后我們實(shí)現(xiàn)一個(gè)SLIDE讓我們可以從一個(gè)基本的邱奇數(shù)有序?qū)?/strong>PAIR[ZERO][ZERO]開始滑動(dòng),當(dāng)我們滑動(dòng)n次的時(shí)候就會(huì)得到n邱奇數(shù)以及n-1邱奇數(shù)类浪。利用這兩個(gè)函數(shù)我們可以實(shí)現(xiàn)一個(gè)PRED函數(shù)载城,通過(guò)這個(gè)函數(shù)我們可以直接獲取到我們作為參數(shù)的邱奇數(shù)的前一個(gè)邱奇數(shù) - 1。最后我們只需要對(duì)一個(gè)數(shù)m反復(fù)調(diào)用PRED函數(shù)n次就能夠得到我們期望的m - n费就。把這個(gè)過(guò)程封裝一下之后就能夠得到我們期望減法SUBT函數(shù)了诉瓦。

4. 階乘

最后我們討論一下階乘,其實(shí)搞懂了前面這些之后,階乘實(shí)現(xiàn)起來(lái)就非常簡(jiǎn)單了睬澡。我們知道乘法m x n就是m從0開始累加nm固额。那么階乘呢?m ^ n其實(shí)就是從1開始連續(xù)乘nm猴贰。原理知道了对雪,代碼其實(shí)也就很好寫了。

POWER = -> m {
  -> n {
    n[MULTI[m]][ONE]
  }
}

最后檢驗(yàn)運(yùn)算結(jié)果

>> p to_integer(POWER[FIVE][FIVE])
3125

>> p to_integer(POWER[FOUR][FOUR])
256

>> p to_integer(POWER[FOUR][ONE])
4

5. 尾聲

用《我編程米绕,我快樂(lè)》里面的一則建議來(lái)說(shuō)就是:“如果你想學(xué)懂某一種技術(shù)瑟捣,就把它教給別人吧≌じ桑”相關(guān)的代碼片段已經(jīng)貼到了gist迈套。

Step by Step

不好意思,又一口氣啰嗦了一大堆碱鳞,分別講了加法桑李,減法乘法窿给, 階乘這些運(yùn)算的Lambda實(shí)現(xiàn)方式贵白。如果你覺得作者表達(dá)得不夠清晰的話,還可以參考一下這些文章崩泡。

Introduction to Lambda Calculus: 簡(jiǎn)單地講解了Lambda實(shí)現(xiàn)基本數(shù)值運(yùn)算的基礎(chǔ)知識(shí)禁荒。
Lambda Calculus: Subtraction is hard: 詳細(xì)講解了如何用Lambda演算來(lái)實(shí)現(xiàn)減法。
Lambda 演算Wiki:簡(jiǎn)單講解Lambda演算的一些基礎(chǔ)知識(shí)角撞。

私人推薦書籍
《我編程呛伴,我快樂(lè)》:一本關(guān)于編程還有就業(yè)的好書,正在讀第二遍感覺很值得一讀谒所。

Happy Coding!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末热康,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子劣领,更是在濱河造成了極大的恐慌姐军,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剖踊,死亡現(xiàn)場(chǎng)離奇詭異庶弃,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)德澈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)固惯,“玉大人梆造,你說(shuō)我怎么就攤上這事。” “怎么了镇辉?”我有些...
    開封第一講書人閱讀 164,911評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵屡穗,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我忽肛,道長(zhǎng)村砂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,737評(píng)論 1 294
  • 正文 為了忘掉前任屹逛,我火速辦了婚禮础废,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘罕模。我一直安慰自己评腺,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,753評(píng)論 6 392
  • 文/花漫 我一把揭開白布淑掌。 她就那樣靜靜地躺著蒿讥,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抛腕。 梳的紋絲不亂的頭發(fā)上芋绸,一...
    開封第一講書人閱讀 51,598評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音担敌,去河邊找鬼摔敛。 笑死,一個(gè)胖子當(dāng)著我的面吹牛柄错,可吹牛的內(nèi)容都是我干的舷夺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,338評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼售貌,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼给猾!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起颂跨,我...
    開封第一講書人閱讀 39,249評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤敢伸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后恒削,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體池颈,經(jīng)...
    沈念sama閱讀 45,696評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,888評(píng)論 3 336
  • 正文 我和宋清朗相戀三年钓丰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躯砰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,013評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡携丁,死狀恐怖琢歇,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤李茫,帶...
    沈念sama閱讀 35,731評(píng)論 5 346
  • 正文 年R本政府宣布揭保,位于F島的核電站,受9級(jí)特大地震影響魄宏,放射性物質(zhì)發(fā)生泄漏秸侣。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,348評(píng)論 3 330
  • 文/蒙蒙 一宠互、第九天 我趴在偏房一處隱蔽的房頂上張望味榛。 院中可真熱鬧,春花似錦名秀、人聲如沸励负。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)继榆。三九已至,卻和暖如春汁掠,著一層夾襖步出監(jiān)牢的瞬間略吨,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工考阱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翠忠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,203評(píng)論 3 370
  • 正文 我出身青樓乞榨,卻偏偏與公主長(zhǎng)得像秽之,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子吃既,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,960評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容