函數式編程

v2-5072f9061e3bd1c5d8457bdeff24064c_r

有關jMiniLang的說明:http://files.cnblogs.com/files/bajdcc/jMiniLang-manual.pdfhtml

================================java

看了Haskell 這段代碼該如何理解?今天突發奇想,嘗試一把函數式編程的感受~git

v2-3b2c7ba9dd8d494555bd2b260c07e87f_r

最近把bajdcc/jMiniLang繼續修整了一下,修了點bug,界面開了抗鋸齒,頓時美觀度大增哈哈。github

依照【遊戲框架系列】詩情畫意 - 知乎專欄的慣例,增長了一言 - ヒトコト - Hitokoto.us以測試Web的效果,當前的網絡線程是在main thread中操做的,之後會放在worker thread中實現異步。編程

本文要完成幾個任務:數組

  1. 實現add,add接受一個數組做爲參數,使命是將數組中全部元素相加
  2. 實現字符串翻轉reverse,如將12345翻成54321
  3. 實現haskell中的<*> Applicative功能
  4. 實現迴文數判斷

預備知識:Lambda

其實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綁定的環境中查找變量,找不到的話再找外層變量。通過實踐,這個方法管用。

基本函數

https://github.com/bajdcc/jMiniLang/blob/master/src/priv/bajdcc/LALR1/interpret/module/ModuleFunction.java
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);
  • g_func_eq 等於 ==
  • str 例如 12321
  • reverse 即字符串翻轉函數

因此原式即:str == reverse(str)。

總結

匆匆忙忙只是完成了函數式編程的第一步,後面會慢慢補上幾個好玩的特性,如map、fold等。g_func_apply其實等同於Haskell中的foldl。

https://zhuanlan.zhihu.com/p/26804202備份。


(二)

v2-48ae07a0298a343b5869b6f7083069b5_r

寫在前面

接過一番折騰,終於實現了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的功能。我把中文和英文弄成同樣大小,方便輸出。

Fold的應用

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等功能。

Zip的實現

因爲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就絕了。

https://zhuanlan.zhihu.com/p/26912674備份。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息