花了两周时间撸了个好玩的 JS 项目,玩转函数式编程

2015-10-30 10:15:30 +08:00
 icymorn
# Lambda-Lite-Js

这是我最近使用 javascript 实现的一个函数式小语言,我把它叫做 Lambda-Lite-js (简称: LL 语言),满足各位前端程序员在函数式语言中对装逼的需求。先上 demo 。

示例及在线 demo: http://icymorn.github.io/lambda-lite-js/ (注意每个语句后要有分号,有些语法错误请到控制台看,错误提示还不完善,见谅)
github: https://github.com/icymorn/lambda-lite-js


## 特点

语法类似 Haskell ,我在其中实现了很多好玩的特性,比如,`lambda 演算`,`柯里化函数`,`延迟计算`,`模式匹配`,`Point-Free 风格`。

虽说的函数式语言,但是特别提供了中缀运算符的调用,比如 `+ - * /`。支持的类型有: boolean 、 string 、 number 、 list 。

内置函数:`print`,`length`、`reverse` 等(等后续完善)。

先来一发阶乘计算。

```haskell
let fact n =
if n == 1 then 1 else n * (fact n - 1);
print (fact 5);
```

现在我不讲语法,先讲一下好玩的特性:

## Point-Free 风格

函数式语言的函数调用是左结合的,所以很多时候需要大量的括号,这是一种反人类的做法(没错,我说的是 Lisp ),这时候你可以用 `$` 来改变当前函数结合顺序

```haskell
let double n = n + n;
print (double 10);
print $ double 10;
```

需要使用现有函数组合成更强大的函数?那么类似 haskell 中的 `.` 可以做到。计算 `n * n + n * n` 的式子可以用 `n + n` 和 `n * n` 组合起来

```haskell
let double = \n -> n + n;
let square = \n -> n * n;
let func = double . square;
print $ func 10;
```

## Lambda 演算与不动点组合子

各位细心的话,可以发现我在上面的示例中用了两种声明函数的方法:

* \n -> n + n;
* let double n = n + n;

前一种是纯的 Lambda 表达式,即匿名函数,有且仅有一个参数,但是可以组合出神奇的效果。比如 Lambda 组合出多参数函数。

```haskell
let add = \a -> \b -> a + b;
print $ add 1 2;
```

后一种是我添加的语法糖,但是好像更好看一点,支持多参数函数声明(实际还是 lambda 函数组合)下面是一个柯里化的例子:

```haskell
let add a b = a + b;
let add3 = add 3;
print $ add3 4;
print $ add3 5;
```
以上通过生成一个新函数 add3 ,固定了 add 函数的第一个参数,最后输出 7 8 ;

还有更有意思的不动点组合子,由于匿名函数没有名字,通常是没法递归自己的(除非使用 let 命名了)。这时候轮到 z-combinator 出场,由于 ll 语言在调用函数时是 call-by-value 的,所以不能用 y-combinator 。 z 组合子表达形式是这样的 ` \f -> (\x -> f (\y -> x x y)) (\x -> f (\y -> x x y))`

又一个阶乘函数:

```haskell
let z = \f -> (\x -> f (\y -> x x y)) (\x -> f (\y -> x x y));
let makeFact = \g -> \n -> if n < 2
then 1
else n * (g n - 1);
let fact = z makeFact;
print (fact 5);
```

## 模式匹配

haskell 语言中最棒的特性之一就是模式匹配, ll 中也有基本的模式匹配特性,虽然语法不太好看~

继续用阶乘例子:

```haskell
let fact n@1 = 1;
let fact n@Number = n * (fact n - 1);
print $ fact 5;
```

当参数为 1 时, fact 返回 1 ,否则返回 n * (fact n - 1), 这比开头的那个更简洁,不需要再手写 if else then 了。

使用通配符还能做通用匹配。

```haskell
let echo a@Number = print 'Number';
let echo a@String = print 'String';
let echo a@* = print 'Other';

echo 100;
echo "hello";
echo true;
```

使用模式匹配需要注意的是, 同名函数的参数长度必须相同, 每个参数的描述都要用 @ 隔开并且有描述符. 同名函数的参数顺序和名字要一样。

持续完善中...

欢迎点赞。
5147 次点击
所在节点    分享创造
14 条回复
fo2w
2015-10-30 11:18:09 +08:00
我不光点赞了, 我还粉了.....
支持同性交友
irainy
2015-10-30 11:35:47 +08:00
手动点赞
tkisme
2015-10-30 11:52:08 +08:00
为什么不玩回调,闭包之类的
icymorn
2015-10-30 12:32:13 +08:00
@tkisme2013
闭包比较容易嘛,而且在 LL 语言中我自己实现了闭包。回调就更直观了。还有好多更新奇的东西等待发掘。
icymorn
2015-10-30 12:33:21 +08:00
@fo2w 感谢支持
zhy0216
2015-10-30 12:38:39 +08:00
由于 ll 语言在调用函数时是 call-by-value 的,所以不能用 y-combinator . Scheme 就是 call-by-value 的, 可以用 Y 啊... 这句话没读懂
icymorn
2015-10-30 12:39:54 +08:00
@irainy
被浙大大大 follow 了,受宠若惊,感谢支持。
icymorn
2015-10-30 13:22:34 +08:00
@zhy0216

虽然我并不熟 scheme ,不过我来尝试给你回答一下。在 call-by-value 语言中,参数会被事先计算出来,而 y-combinator 会因此陷入死循环。
这是标准的 Y = \f.(\x.f(x x)) (\x.f(x x))
但是在 scheme 中,你会发现其实
(define Y
(lambda (h)
((lambda (x) (h (lambda (a) ((x x) a))))
(lambda (x) (h (lambda (a) ((x x) a)))))))
里面多套了层 lambda ,阻止了进一步的求值,你对比一下我给出的 z combinator ,其实是一样的,只不过大家都习惯性叫 y 而已。
zado
2015-10-30 13:34:31 +08:00
楼主一定是 JavaScript 高手,我做了一个在线执行 js 的东东,我希望把它做得功能完善一点,可是 js 语言我学得不多,你的代码能不能放在上面执行,如果不能是为什么? http://zxxq.sinaapp.com/zxjb.html 能不能帮我看一下?
icymorn
2015-10-30 16:31:51 +08:00
@zado
应该不能的,像 require 和 exports 我都是有重新定义的,才能使我的代码在浏览器和 node.js 中可以用同一份,代码间的相互依赖要改起来也是挺麻烦的。
amaranthf
2015-10-30 18:53:20 +08:00
首先人家 lisp 不是函数式语言……另外,括号多么优美!
icymorn
2015-10-30 19:00:19 +08:00
@amaranthf
lisp 都不算函数式语言… 你这定义太严格了吧。
zhy0216
2015-10-30 21:56:28 +08:00
@icymorn 确实如此, 涨见识了, thx
Kabie
2015-10-31 18:25:51 +08:00
…………赶紧看看。。。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/232203

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX