标签:情况 技术 输出 例子 reg 代码 JD 字符串翻转 lse
有关jMiniLang的说明:http://files.cnblogs.com/files/bajdcc/jMiniLang-manual.pdf
================================
看了Haskell 这段代码该如何理解?今天突发奇想,尝试一把函数式编程的感觉~
最近把bajdcc/jMiniLang继续修整了一下,修了点bug,界面开了抗锯齿,顿时美观度大增哈哈。
依照【游戏框架系列】诗情画意 - 知乎专栏的惯例,增加了一言 - ヒトコト - 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实现了 个调用自身的功能。
不管那么多,这里需要实现这种闭包机制,思路也很简单,就是给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就绝了。
由https://zhuanlan.zhihu.com/p/26912674备份。
标签:情况 技术 输出 例子 reg 代码 JD 字符串翻转 lse
原文地址:https://www.cnblogs.com/bajdcc/p/8972975.html