如何不用洋文编程

不用洋文生成前一百个素数

前些天学了点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. 切记,所有代码改写策略都要在非严格模式下执行,否则对变量的定义行为会产生洋文。