zoukankan      html  css  js  c++  java
  • 函数式编程

    v2-5072f9061e3bd1c5d8457bdeff24064c_r

    有关jMiniLang的说明:http://files.cnblogs.com/files/bajdcc/jMiniLang-manual.pdf

    ================================

    看了Haskell 这段代码该如何理解?今天突发奇想,尝试一把函数式编程的感觉~

    v2-3b2c7ba9dd8d494555bd2b260c07e87f_r

    最近把bajdcc/jMiniLang继续修整了一下,修了点bug,界面开了抗锯齿,顿时美观度大增哈哈。

    依照【游戏框架系列】诗情画意 - 知乎专栏的惯例,增加了一言 - ヒトコト - 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实现了 个调用自身的功能。

    不管那么多,这里需要实现这种闭包机制,思路也很简单,就是给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备份。

  • 相关阅读:
    若没有任何实例包含Class Body 则enum被隐式声明为final
    Effective Java —— 多字段下考虑使用建造者模式构建实例
    Effective Java —— 用静态工厂方法代替构造器
    Java动态代理和CGLib代理
    Netty + Spring + ZooKeeper搭建轻量级RPC框架
    Netty学习摘记 —— UDP广播事件
    Netty学习摘记 —— 简单WEB聊天室开发
    Netty学习摘记 —— 心跳机制 / 基于分隔符和长度的协议
    Bugku 杂项 这是一张单纯的图片
    Bugku 杂项 签到题
  • 原文地址:https://www.cnblogs.com/bajdcc/p/8972975.html
Copyright © 2011-2022 走看看