有關jMiniLang的說明:http://files.cnblogs.com/files/bajdcc/jMiniLang-manual.pdfhtml
================================java
看了Haskell 這段代碼該如何理解?今天突發奇想,嘗試一把函數式編程的感受~git
最近把bajdcc/jMiniLang繼續修整了一下,修了點bug,界面開了抗鋸齒,頓時美觀度大增哈哈。github
依照【遊戲框架系列】詩情畫意 - 知乎專欄的慣例,增長了一言 - ヒトコト - Hitokoto.us以測試Web的效果,當前的網絡線程是在main thread中操做的,之後會放在worker thread中實現異步。編程
本文要完成幾個任務:數組
其實lambda只是沒有名字的函數(事實上我仍是給它們命名了),更加關鍵的地方是閉包,若是沒有閉包,那lambda只是一個沒有卵用的語法糖罷了。安全
var xs = func ["數組遍歷閉包"] ~(l) { var len = call g_array_size(l); var idx = 0; var f = func ~() { if (idx == len) { return g__; } var d = call g_array_get(l, idx); idx++; var f2 = func ~() -> d; return f2; }; return f; };
上面是一個例子,f以及f2是一個lambda,然而在lambda中居然出現了外層變量的引用。沒錯,這就是難點所在。網絡
看另外一個:閉包
var ff = func ~(f) { var fh = func ~(h) { return call h(h); }; var fx = func ~(x) { var fn = func ~(n) { var vx = call x(x); var vf = call f(vx); return call vf(n); }; return fn; }; return call fh(fx); }; var fact = func ~(f) { var fk = func ~(n) { if (n > 0) { return n * call f(n - 1); } else { return 1; }; }; return fk; }; var ffact = call ff(fact); var fact_5 = call ffact(5); call g_printn(fact_5);
上面這個例子就比較玄乎了,有關知識在此Y Combinator - bajdcc - 博客園,用lambda實現了 個調用自身的功能。app
無論那麼多,這裏須要實現這種閉包機制,思路也很簡單,就是給lambda加一層環境,把綁定的變量塞進這環境中,那麼調用執行的時候,首先從lambda綁定的環境中查找變量,找不到的話再找外層變量。通過實踐,這個方法管用。
var max = func ~(a, b) -> a > b ? a : b; var min = func ~(a, b) -> a < b ? a : b; var lt = func ~(a, b) -> a < b; var lte = func ~(a, b) -> a <= b; var gt = func ~(a, b) -> a > b; var gte = func ~(a, b) -> a >= b; var eq = func ~(a, b) -> a == b; var neq = func ~(a, b) -> a != b; var add = func ~(a, b) -> a + b; var sub = func ~(a, b) -> a - b; var mul = func ~(a, b) -> a * b; var div = func ~(a, b) -> a / b;
上面定義了一些函數,全是二元的,比較簡單和經常使用。
var curry = func ~(a, b) { var f = func ~(c) -> call a(b, c); return f; }; var swap = func ~(a) { var f = func ~(b, c) -> call a(c, b); return f; };
上面定義了curry和swap。curry就是先綁定函數的第一參數,調用時只須要提供第二個參數便可。swap的參數是一個二元函數,它的用處就是將兩個參數互換。
先解決第一個問題:實現add函數。
var add = func ~(list) { var sum = 0; foreach (var i : call g_range_array(list)) { sum += i; } return sum; };
先除去數組爲空的例外狀況,上面add的實現代碼是循環結構,沒有什麼好斟酌的了。然而它並無函數式編程那樣的逼格,咱們但願的style是:
func add(list) { if (call g_array_empty(list)) { return 0; } var head = call g_array_head(list); // 獲得數組第一個元素 var tail = call g_array_tail(list); // 獲得去除第一個元素後的數組 return head + call add(tail); };
有了遞歸纔有意思嘛!獲得head和tail的代碼就能夠略去了,細心的人可能發現:獲得tail數組是重建一個新數組呢仍是直接在原有數組上修改?若是是原有基礎上修改,那麼就破壞了數組的只讀性,致使麻煩;若是是new一個新數組,那麼就要給GC添加更新難題了。
因此,妥協之下,只能new新數組了嗎?注意到數組的只讀性,其實只要獲取索引就能夠了!假如數組是1,2,3,4,5...那麼如今給list包裝一下,獲得f=decorate(list),要求f()的結果是1,2,3,4,5....,到末尾給個null,這能實現嗎?
以往的思想,調用一個純函數,不管多少次、什麼狀況下,函數的結果是一成不變的。但是如今我卻要求它每次調用的結果不同!那麼它就再也不是純函數了,它一定有所依賴。這個依賴我能夠顯現給出,也能夠懶得去作。那麼後者就是閉包。
下面實現的關鍵:
xs函數的使命是將做爲數組的參數l進行包裝,返回一個 閉包closure,若是數組l沒有訪問到結尾,那麼調用closure()就會返回一個lambda(調用 lambda()後獲得數據),若是訪問到結尾,返回null。
var xs = func ["數組遍歷閉包"] ~(l) { var len = call g_array_size(l); var idx = 0; var f = func ~() { if (idx == len) { return g__; // =null } var d = call g_array_get(l, idx); idx++; // 索引自增 var f2 = func ~() -> d; return f2; }; return f; };
xs這個閉包返回了一個可調用函數f,而f依賴的len和idx處於xs內部,外界不可能訪問到,所以是安全的。
那如何使用這個閉包呢?
var g_func_1 = func ~(a) -> a; // 返回自身 var g_func_apply = func ~(name, list) { return call g_func_apply_arg(name, list, "g_func_1"); }; export "g_func_apply"; var g_func_apply_arg = func ~(name, list, arg) { // 帶參數版本 var len = call g_array_size(list); // 計算大小 if (len == 0) { return g__; } // 返回空 if (len == 1) { return call g_array_get(list, 0); } // 返回第一個 var x = call xs(list); var val = call x(); let val = call val(); var n = name; let n = call arg(n); // 裝飾原有函數,如調換兩參數位置啊 for (;;) { var v2 = call x(); // 取數組下一個數 if (call g_is_null(v2)) { break; } // 遇到結尾 let v2 = call v2(); let val = call n(val, v2); // 累計處理 } return val; }; export "g_func_apply_arg";
x就是做爲一個閉包,每次調用x(),返回一個lambda,執行後獲得值。將值進行累加計算:call n(val, v2),其中n就是要調用的二元基本函數,在文中前面列舉了。不斷累計,直到閉包返回一個null,停止循環,返回計算結果。
g_func_apply(name, list)的意義:將list數組進行累計(參照C#中Aggregate函數),用name指代的二元操做做爲銜接。
g_func_apply_arg(name, list, arg)的意義:將list數組進行累計(參照C#中Aggregate函數),用name指代的二元操做做爲銜接,其中二元操做經arg函數修飾(如arg=swap時且結合不知足交換律時,結果就是反的)。
那麼到這裏,第一個任務完成了!
字符串翻轉
var g_string_reverse = func ~(str) -> call g_func_apply_arg("g_func_add", call g_string_split(str, ""), "g_func_swap");
這裏的竅門在於swap函數。
Haskell 中的 <*> Applicative
var g_func_applicative = func ~(f, a, b) -> call f(a, call b(a));
實際意義是:applicative(f, a, b) = f(a, b(a))
迴文數判斷
var val = call g_func_applicative("g_func_eq", str, reverse);
因此原式即:str == reverse(str)。
匆匆忙忙只是完成了函數式編程的第一步,後面會慢慢補上幾個好玩的特性,如map、fold等。g_func_apply其實等同於Haskell中的foldl。
由https://zhuanlan.zhihu.com/p/26804202備份。
(二)
接過一番折騰,終於實現了fold,take,map,zip等函數,雖然說與haskell比還差得遠,我也沒作啥優化,就是體會一下函數式的風格。
簡而言之,就是將函數做爲參數傳來傳去,因此除了傳值以外,還能夠傳行爲。
分析了下haskell的主要函數,概括起來,發現fold函數是很是基礎的,它就至關於卷積,將一個表拍成一個元素。
對於表list,通常有兩部分 x:xs,表頭和表尾,haskell的實現是將表切開成x:xs,不過有了fold以後,就能夠忽略x:xs了。
咱們先設計兩個list遍歷函數:
var g_func_xsl = func ["數組遍歷閉包-foldl"] ~(l) { var len = call g_array_size(l); var idx = 0; var _xsl = func ~() { if (idx == len) { return g__; } var d = call g_array_get(l, idx); idx++; var _xsl_ = func ~() -> d; return _xsl_; }; return _xsl; }; export "g_func_xsl"; var g_func_xsr = func ["數組遍歷閉包-foldr"] ~(l) { var idx = call g_array_size(l) - 1; var _xsr = func ~() { if (idx < 0) { return g__; } var d = call g_array_get(l, idx); idx--; var _xsr_ = func ~() -> d; return _xsr_; }; return _xsr; }; export "g_func_xsr";
其中,xsl是從左向右遍歷,xsr則是反過來。作這個閉包的好處是:不建立新的list。
再建立幾個默認值:
var g_func_1 = func ~(a) -> a; export "g_func_1"; var g_func_always_1 = func ~(a) -> 1; export "g_func_always_1"; var g_func_always_true = func ~(a) -> true; export "g_func_always_true";
接下來纔是主角——fold函數。
var g_func_fold = func [ "函數名:g_func_fold", "參數解釋:", " - name: 套用的摺疊函數", " - list: 需處理的數組", " - init: 初始值(不用則爲空)", " - xs: 數組遍歷方式(xsl=從左到右,xsr=從右到左)", " - map: 對遍歷的每一個元素施加的變換", " - arg: 對二元操做進行包裝(默認=g_func_1,例=g_func_swap)", " - filter: 對map後的元素進行過濾(true則處理)" ] ~(name, list, init, xs, map, arg, filter) { var len = call g_array_size(list); if (len == 0) { return g__; } // 確定返回空 var val = g__; var x = g__; if (call g_is_null(init)) { // 沒初值的話,取第一個元素爲初值 if (len == 1) { return call g_array_get(list, 0); } // 只有一個元素 let x = call xs(list); // 建立遍歷閉包 let val = call x(); // 取第一個元素 let val = call val(); let val = call map(val); // 對元素進行變換 } else { let x = call xs(list); let val = init; } var n = name; // 對數組進行變換 let n = call arg(n); // 對卷積方式進行變換 for (;;) { // 遍歷數組 var v2 = call x(); // 取得下一元素 if (call g_is_null(v2)) { break; } // 沒有下一元素,停止 let v2 = call v2(); // 下一元素 let v2 = call map(v2); // 對下一元素進行變換 if (call filter(v2)) { // 過濾控制 let val = call n(val, v2); // 將兩元素進行處理 } } return val; }; export "g_func_fold";
作了一個看doc的功能。我把中文和英文弄成同樣大小,方便輸出。
var g_func_apply = func ~(name, list) -> call g_func_apply_arg(name, list, "g_func_1"); export "g_func_apply"; var g_func_apply_arg = func ~(name, list, arg) -> call g_func_fold(name, list, g__, "g_func_xsl", "g_func_1", arg, "g_func_always_true"); export "g_func_apply_arg"; var g_func_applyr = func ~(name, list) -> call g_func_applyr_arg(name, list, "g_func_1"); export "g_func_applyr"; var g_func_applyr_arg = func ~(name, list, arg) -> call g_func_fold(name, list, g__, "g_func_xsr", "g_func_1", arg, "g_func_always_true"); export "g_func_applyr_arg"; // ---------------------------------------------- var g_func_map = func ~(list, arg) -> call g_func_fold("g_array_add", list, g_new_array, "g_func_xsl", arg, "g_func_1", "g_func_always_true"); export "g_func_map"; var g_func_mapr = func ~(list, arg) -> call g_func_fold("g_array_add", list, g_new_array, "g_func_xsr", arg, "g_func_1", "g_func_always_true"); export "g_func_mapr"; var g_func_length = func ~(list) -> call g_func_fold("g_func_add", list, 0, "g_func_xsl", "g_func_always_1", "g_func_1", "g_func_always_true"); export "g_func_length"; var g_func_filter = func ~(list, filter) -> call g_func_fold("g_array_add", list, g_new_array, "g_func_xsl", "g_func_1", "g_func_1", filter); export "g_func_filter"; // ---------------------------------------------- var take_filter = func ~(n) { var idx = 0; var end = n; var _take_filter = func ~(a) -> idx++ <= end; return _take_filter; }; var drop_filter = func ~(n) { var idx = 0; var end = n; var _drop_filter = func ~(a) -> idx++ > end; return _drop_filter; }; var g_func_take = func ~(list, n) -> call g_func_fold("g_array_add", list, g_new_array, "g_func_xsl", "g_func_1", "g_func_1", call take_filter(n)); export "g_func_take"; var g_func_taker = func ~(list, n) -> call g_func_fold("g_array_add", list, g_new_array, "g_func_xsr", "g_func_1", "g_func_1", call take_filter(n)); export "g_func_taker"; var g_func_drop = func ~(list, n) -> call g_func_fold("g_array_add", list, g_new_array, "g_func_xsl", "g_func_1", "g_func_1", call drop_filter(n)); export "g_func_drop"; var g_func_dropr = func ~(list, n) -> call g_func_fold("g_array_add", list, g_new_array, "g_func_xsr", "g_func_1", "g_func_1", call drop_filter(n)); export "g_func_dropr";
上面的代碼實踐證實:用fold能夠實現map、filter、take、drop等功能。
因爲fold是對單一數組進行卷積/彙集,而zip的對象是兩個數組,因此不兼容,只好另寫了。
var func_zip = func ~(name, a, b, xs) { var val = []; var xa = call xs(a); var xb = call xs(b); for (;;) { var _a = call xa(); var _b = call xb(); if (call g_is_null(_a) || call g_is_null(b)) { break; } var c = call name(call _a(), call _b()); call g_array_add(val, c); } return val; }; var g_func_zip = func ~(name, a, b) -> call func_zip(name, a, b, "g_func_xsl"); export "g_func_zip"; var g_func_zipr = func ~(name, a, b) -> call func_zip(name, a, b, "g_func_xsr"); export "g_func_zipr";
整體跟fold差很少。
jMiniLang我實現了閉包,就能夠大搞函數式編程了。其實裏面更復雜的是類型判斷、代碼優化等內容,因此這裏也就是嚐嚐鮮嚐了。等到把bajdcc/CParser改一下,以c語言去支持lambda就絕了。