邏輯式程式語言極簡實現(使用C#) - 1. 邏輯式程式語言介紹
阿新 • • 發佈:2020-06-28
相信很多朋友對於邏輯式程式語言,都有一種最熟悉的陌生人的感覺。一方面,平時在書籍、在資訊網站,偶爾能看到一些吹噓邏輯式程式設計的話語。但另一方面,也沒見過周圍有人真正用到它(除了SQL)。
遙記當時看《The Reasoned Schemer》(一本講邏輯式程式語言的小人書),被最後兩頁的直譯器實現驚豔到了。看似如此複雜的計算邏輯,其實現竟然這麼簡潔。不過礙於當時水平有限,也就囫圇吞棗般看了過去。後來有一天,不知何故腦子靈光一閃,把圖遍歷和流計算模式聯絡在一起,瞬間明白了《The Reasoned Schemer》中的做法。動手寫了寫程式碼,果然如此,短短[兩百來行程式碼](https://github.com/sKabYY/palestra/blob/master/reasoned/mk.ss),就完成了直譯器的實現,才發現原來如此簡單。很多時候,並非問題本身有多難,只是沒有想到正確的方法。
本系列將盡可能簡潔地說明邏輯式程式設計語音的原理,並實現一門簡單的邏輯式程式語言。考慮到C#的使用者較多,因此選擇用C#來實現。實現的這門語言就叫NMiniKanren。文章總體內容如下:
* NMiniKanren語言介紹
* 語言基礎
* 一道有趣的邏輯題:誰是凶手
* NMiniKanren執行原理
* 構造條件關係圖,遍歷分支
* 代入消元法解未知量
* 實現NMiniKanren
* 流計算模式簡介
* 代入消元法的實現
* 遍歷分支的實現
故事從兩個正在吃午餐的程式設計師說起。
老明和小皮是就職於同一家傳統企業的程式設計師。這天,兩人吃著午餐。老明邊吃邊刷著抖音,鼻孔時不時噴出幾條米粉。
小皮是一臉麻木地刷著求職網和資訊網,忽然幾個大字映入眼底:《新型邏輯式程式語言重磅出世,即將顛覆IT界!》小皮一陣好奇,往下一翻,結果接著的是一些難懂的話,什麼“一階邏輯”,什麼“合一演算法”,以及鬼畫符似的公式之類。
小皮看得索然無味,但被勾引起來的對邏輯式程式設計的興趣彷彿~~澳洲~~森林大火一樣難以平息。於是伸手拍下老明高舉手機的左手,問道:“嘿!邏輯式程式設計有了解過麼?是個啥玩意兒?”
“邏輯式程式設計啊……嘿嘿,前段時間剛好稍微瞭解了一下。”老明鼻孔朝天吸了兩口氣,“我說的稍微瞭解,是指實現了一門邏輯式程式語言。”
“不愧是資深老IT,瞭解也比別人深入一坨坨……”
“也就比你早來一年好不好……我是一邊看一本奇書一邊做的。Dan老師(Dan Friedman)寫的《The Reasoned Schemer》。這本書挺值得一看的,書中使用一門教學用的邏輯式程式語言,講解這門語言的特性、用法、以及原理。最後還給出了這門語言的實現。核心程式碼只用了兩頁紙。
![](https://img2020.cnblogs.com/blog/576869/202006/576869-20200627220458829-1626404081.jpg)
“所謂邏輯式程式設計,從使用上看是把宣告式程式設計發揮到極致的一種程式設計正規化。普通的程式語言,大部分還是基於指令式程式設計,需要你告訴機器每一步執行什麼指令。而邏輯式程式設計的理念是,我們只需要告訴機器我們需要的目標,機器會根據這個目標自動探索執行過程。
“**邏輯式程式設計的特點是可以反向執行。你可以像做數學題一樣,宣告未知量,列出方程,然後程式會為你求解未知量。**”
![](https://img2020.cnblogs.com/blog/576869/202006/576869-20200627220400214-349321078.png)
“挺神奇的。聽起來有點像AI程式設計。不過這麼高階的東西怎麼沒有流行起來?感覺可以節省不少人力。”小皮忽然有種飯碗即將不保的感覺。
“嘿嘿……想得美。其實邏輯式程式設計,既不智慧,也不好用。你回憶一下你中學的時候是怎麼解方程組的?”
“嗯……先盯一會方程組,看看它長得像不像有快捷解法的樣子。看不出來的話就用代入法慢慢算。這和邏輯式程式設計有什麼關係?”
“**邏輯式程式設計並不智慧,它只是把某種類似代入法的通用演算法內建到直譯器裡**。邏輯式程式語言寫的程式執行時,不過是根據通用演算法進行求解而已。它不像人一樣會去尋找更快捷的方法,同時也不能解決超綱問題。
![](https://img2020.cnblogs.com/blog/576869/202006/576869-20200628095521604-908166855.png)
“**而且邏輯式程式語言的學習成本也不低**。如果你要用好這門語言,你得把它使用的通用演算法搞清楚。雖然你寫的宣告式的程式碼,但內心要時刻清楚程式的執行過程。**如果你拿它當個黑盒來用,那很可能你寫出來的程式的執行效率會非常低,甚至跑出一些莫名其妙的結果**。”
“哦哦,要學會用它,還得先懂得怎麼實現它。這學習成本還挺高的。”小皮跟著吐槽,不過他知道老明表明上看似嫌棄邏輯式程式設計的實用性,私底下肯定玩得不亦樂乎,並且也喜歡跟別人分享。於是小皮接著道:“雖然應該是用不著,但感覺挺有意思的,再仔細講講唄。天天寫CRUD,腦子都淡出個鳥了。”
果然老明坐直起來:“《The Reasoned Schemer》用的這門邏輯式程式語言叫miniKanren,用Scheme/Lisp實現的。去年給你安利過Scheme了,現在掌握得怎麼樣?”
“一竅不通……”小皮大窘。去年到現在,小皮一直很忙,並沒有自學什麼東西。如果沒有外力驅動的話,他還將一直忙下去。
“果然如此。所以我順手也實現了個C#魔改版本的miniKanren。就叫NMiniKanren。我把NMiniKanren實現為C#的一個DSL。這樣的好處是方便熟悉C#或者Java的人快速上手;壞處是DSL會受限於C#語言的能力,程式碼看起來沒有Scheme版那麼優雅。”老明用左手做了個打引號的動作,“先從簡單的例子開始吧。比如說,有個未知量`q`,我們的目標是讓`q`等於5或者等於6。那麼滿足條件的`q`值有哪些?”
“不就是5和6麼……這也太簡單了吧。”
“Bingo!”老明打了個響指,“我們先用簡單的例子看看程式碼結構。”只見老明兩指輕輕夾住一隻筷子,勾出幾條米粉,快速在桌上擺出如下程式碼:
```CSharp
// k提供NMiniKanren的方法,q是待求解的未知變數。
var res = KRunner.Run(null /* null表示輸出所有可能的結果 */, (k, q) =>
{
// q == 5 或者 q == 6
return k.Any(
k.Eq(q, 5),
k.Eq(q, 6));
});
KRunner.PrintResult(res); // 輸出結果:[5, 6]
```
“程式碼中,`KRunner.Run`用於執行一段NMiniKanren程式碼,它的宣告如下。”老明繼續撥動米粉:
```CSharp
public class KRunner
{
public static IList