V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
icymorn
V2EX  ›  分享创造

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

  •  
  •   icymorn ·
    moevis · 2015-10-30 10:15:30 +08:00 · 5130 次点击
    这是一个创建于 3316 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 。

    内置函数:printlengthreverse 等(等后续完善)。

    先来一发阶乘计算。

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

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

    Point-Free 风格

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

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

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

    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 组合出多参数函数。

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

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

    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))

    又一个阶乘函数:

    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 中也有基本的模式匹配特性,虽然语法不太好看~

    继续用阶乘例子:

    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 了。

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

    let echo a@Number = print 'Number';
    let echo a@String = print 'String';
    let echo a@*      = print 'Other';
    
    echo 100;
    echo "hello";
    echo true;
    

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

    持续完善中...

    欢迎点赞。

    14 条回复    2015-10-31 18:25:51 +08:00
    fo2w
        1
    fo2w  
       2015-10-30 11:18:09 +08:00
    我不光点赞了, 我还粉了.....
    支持同性交友
    irainy
        2
    irainy  
       2015-10-30 11:35:47 +08:00 via iPhone
    手动点赞
    tkisme
        3
    tkisme  
       2015-10-30 11:52:08 +08:00
    为什么不玩回调,闭包之类的
    icymorn
        4
    icymorn  
    OP
       2015-10-30 12:32:13 +08:00
    @tkisme2013
    闭包比较容易嘛,而且在 LL 语言中我自己实现了闭包。回调就更直观了。还有好多更新奇的东西等待发掘。
    icymorn
        5
    icymorn  
    OP
       2015-10-30 12:33:21 +08:00
    @fo2w 感谢支持
    zhy0216
        6
    zhy0216  
       2015-10-30 12:38:39 +08:00
    由于 ll 语言在调用函数时是 call-by-value 的,所以不能用 y-combinator . Scheme 就是 call-by-value 的, 可以用 Y 啊... 这句话没读懂
    icymorn
        7
    icymorn  
    OP
       2015-10-30 12:39:54 +08:00
    @irainy
    被浙大大大 follow 了,受宠若惊,感谢支持。
    icymorn
        8
    icymorn  
    OP
       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
        9
    zado  
       2015-10-30 13:34:31 +08:00
    楼主一定是 JavaScript 高手,我做了一个在线执行 js 的东东,我希望把它做得功能完善一点,可是 js 语言我学得不多,你的代码能不能放在上面执行,如果不能是为什么? http://zxxq.sinaapp.com/zxjb.html 能不能帮我看一下?
    icymorn
        10
    icymorn  
    OP
       2015-10-30 16:31:51 +08:00   ❤️ 1
    @zado
    应该不能的,像 require 和 exports 我都是有重新定义的,才能使我的代码在浏览器和 node.js 中可以用同一份,代码间的相互依赖要改起来也是挺麻烦的。
    amaranthf
        11
    amaranthf  
       2015-10-30 18:53:20 +08:00
    首先人家 lisp 不是函数式语言……另外,括号多么优美!
    icymorn
        12
    icymorn  
    OP
       2015-10-30 19:00:19 +08:00 via Smartisan T1
    @amaranthf
    lisp 都不算函数式语言… 你这定义太严格了吧。
    zhy0216
        13
    zhy0216  
       2015-10-30 21:56:28 +08:00
    @icymorn 确实如此, 涨见识了, thx
    Kabie
        14
    Kabie  
       2015-10-31 18:25:51 +08:00
    …………赶紧看看。。。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3260 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 00:37 · PVG 08:37 · LAX 16:37 · JFK 19:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.