有关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就绝了。