前些天学了点js,发现里边的箭头函数是真的好用,从此function关键字消失在了我的代码中。
但随之也发现了个问题,匿名函数遇上需要递归的地方该咋办呢……
在谷歌上不抱任何期待地敲下“匿名函数”“递归”等字样,没想到还真的有这样的操作。
Github上的大佬给出了答案。
https://github.com/Lucifier129/Lucifier129.github.io/issues/7 在 JavaScript
在 JavaScript 中用匿名函数(箭头函数)写出递归的方法
看起来还不错,越看越亢奋(装作看懂了的样子)
根据教程里的指导,求一个数阶乘可以写成这样的形式:
var fact_fac = self => num => num ? num * self(--num) : 1;
var Y = f => (x => n => f(x(x))(n))(x => n => f(x(x))(n));
var fact = Y(fact_fac);
console.log(fact(4));//24
解释一下:
先看第一行,
对于任意的函数self,这里边的fact_fac(源自factorial factory,阶乘工厂)满足
fact_fac(self) <=> num => num ? num * self(--num) : 1;
那么,当self为fact,其中fact也就是我们要的阶乘函数时,神奇的事情发生了!
fact_fac(fact) <=> num => num ? num * fact(--num) : 1;
而后者恰恰时fact函数的实现!!
这意味着有
fact_fac(fact) <=> fact
那么求解fact也就意味着要求解fact_fac的不动点。
而这,恰恰可以由另一个神奇的Y函数求得。具体实现可以自行维基Y组合子以及λ演算。
到这里,你会发现借助Y函数,所有的递归函数都可以用匿名函数表示,具体实现起来就是这样:
function a (...){
...a(...)...
return b;
}
可以等价于
Y = f => (x => n => f(x(x))(n))(x => n => f(x(x))(n));
a = Y(self => (...) => {
...self(...)...
return b;
});
那跟我们的主题“如何不用洋文编程”有什么关系呢?
别急,现在我们已经完成了第一步,彻底摆脱了function关键字。
稍微改造下上述代码,用$和_的组合代替变量名来装逼摆脱洋文。
(标点符号怎么能算洋文呢的!(~ ̄▽ ̄)~)
Y = $_=>(_=>$=>$_(_(_))($))(_=>$=>$_(_(_))($));
a = Y($=>(...)=>{...$(...)...return b;});
(当然,还有糟糕的return,这个呆会搞掉)
事实上,进一步了解js里的匿名函数后可以发现,它与lambda函数完全一致。
而lambda演算时图灵完备的,
这意味着我们的终极目标(不用洋文编程)是可行的!!
第二个需要解决的问题是循环的操作。
但是我们已经有了递归,解决一个while循环并不难。
直接上代码吧。
const Y = f => (x => n => f(x(x))(n))(x => n => f(x(x))(n));
const while_ = Y(self => judge => dosth => judge() && dosth() ^ self(judge)(dosth));
调用的基本语法是
while(a){b} <=> while_(()=>a)(()=>{b})
具体应用如下:
var test = 5;
while_(() => test > 0)(() => {
console.log('yes!!!');
test--;
});
简化一下:
const while_ = ($_=>(_=>$=>$_(_(_))($))(_=>$=>$_(_(_))($)))($=>_=>_$=>_()&&_$()^$(_)(_$));
当然也有相应的for循环版本:
const for_ = ($_=>(_=>$=>$_(_(_))($))(_=>$=>$_(_(_))($)))($=>_=>__=>$_=>_$=>{(_&&_())^__()&&_$()^$_()^$()(__)($_)(_$)});
调用的基本语法是:
for(a;b;c){d} <=> for_(()=>{a})(()=>b)(()=>{c})(()=>{d})
嫌for太丑太长,甚至自己造了个repeat函数,重复指定代码若干次。
const Y = f => (x => n => f(x(x))(n))(x => n => f(x(x))(n));
const repeat_ = Y(self => num => func => num && self(--num)() ^ func());
const repeat_ = ($_=>(_=>$=>$_(_(_))($))(_=>$=>$_(_(_))($)))($=>__=>_=>__&&$(--__)(_)^_());
相当于:
for(var i=0;i<a;i++){b/*without using i*/} <=> repeat_(a)(()=>{b})
OK,到此为止解决循环。
下一个问题是如何解决if等条件判断,这个好办,基本也有以下两种解决方案:
一种是三目运算符?:——不多说了。
另一种是利用&&和||以及&,|,^的性质:
if(a){b} <=> a&&b;
if(a){b}else{c} <=> a&&(b||1)||c;
1太丑,可以换成!0
两个语句间的分号也可以换成|、&、^等符号。
之前说的return也可以在这里解决。
注意到箭头函数箭头后可以直接返回值,那么可以采用以下的替换:
(a)=>{b; return c;} <=> (a)=>(b)&&0||c
善用++,可以让代码更简洁。
基本的操作就这些了,最后贴上开头处图片中的代码,欢迎大佬破译:
primes_below=$_$=>($=[])&($_=$)&(__=[])|(_=__)|_++^++_|($_=>(_=>$=>$_(_(_))($))(_=>$=>$_(_(_))($)))($=>_=>_$=>_()&&_$()^$(_)(_$))(()=>_<=$_$)(()=>{(_$=0)&(!__[_]&&($[$_++]=_))^($_=>(_=>$=>$_(_(_))($))(_=>$=>$_(_(_))($)))($=>_=>_$=>_()&&_$()^$(_)(_$))(()=>_$<=$_&&_*$[_$]<=$_$&&(_$==0||_%$[_$-!0]))(()=>{(__[_*$[_$]]=!0)|_$++})^_++})&&0||$;
console.log(primes_below(100));
PS. 切记,所有代码改写策略都要在非严格模式下执行,否则对变量的定义行为会产生洋文。