V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
notcome
V2EX  ›  Node.js

我真的不知道如何描述这个问题,跟函数式编程有关,求助

  •  
  •   notcome · 2014-04-19 09:33:31 +08:00 · 4758 次点击
    这是一个创建于 3660 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我打算撸一个 PEG based Markdown Parser,练习一下。
    然后我想写这么一个东西,注意 var = BIContent 一行:

    function BIGen (token) {
    var BIContent = R['/']([token, R['s']([Char, BIContent])]);
    return R['s']([token, Char, BIContent]);
    }

    R['s'] 的参数为一个函数数组,返回一个函数,然后 R['/'] 的参数也是一个函数数组,返回一个函数。我现在希望实现一个递归,但是先求参数值的原因,所以……卡住了。

    怎么办?

    (我好像记得 SICP 中有有关内容,马上看看去)
    第 1 条附言  ·  2014-04-19 15:55:19 +08:00
    function series (first, second) {
    return function (input, index) {
    var result = first(input, index);
    if (result) return result;
    result = second(input, index);
    return {output: result.output, index: result.index};
    }
    }

    嗯, 简化一下模型就是这个样子, 我希望能定义一个函数 f = series(g, f), 用正常的办法来写也就是:

    function second (input, index) {
    var result = first(input, index);
    if (result) return result;
    return second(input, index);
    }

    有什么好的办法? 因为不是每次都要调用自身, 或者这个 series 长度可能会很长, 所以想抽象一下.
    第 2 条附言  ·  2014-04-22 10:00:05 +08:00
    感谢各位帮忙,也为自己之前描述问题不清楚感到抱歉。

    是这样的,我正在学习 PEG (Parsing Expression Grammar) 来打算自己写 parser,一个简单的模型:

    LineBreak <- "\r\n" / "\n" / "r"
    Char <- !LineBreak .
    Comment <- "//" Char* LineBreak / "/*" (!"*/" Char)* "*/"

    左边的是 expression
    . 代表任意字符,* 代表可以有零个或者多个前面的 expr,! 是一个 not predicate,如果后面的 expr 匹配成功则失败,失败则成功,但是它不会消耗字符。/ 代表如果前面一个失败了,则选择后面的,即所谓的 prioritized choose。

    语文能力拙计,所以描述的大家看不懂请见谅啊……有兴趣可以去看这篇论文: http://pdos.csail.mit.edu/~baford/packrat/popl04/peg-popl04.pdf

    然后回到我这个问题。因为我论文没看太懂(后面的数学证明太可怕,我毕竟还年轻),于是打算使用一种比较直观的方式,用 Javascript 动手实现一遍,我是这样定义的。

    function (input, index) -> { output, index } / failed

    满足这样的函数都被称为 expression,input 是给的字符串,index 代表从哪儿开始 consume,然后返回的 output 是结果,index 是指新的 consume starting position。如果失败了,就返回一个对象 failed,其实就是 var failed = {},Javascript 对象都是指针,我没有理解错吧。

    然后我要实现一下上面的这些 not predicate 啊、prioritized choose 啊什么的,就是把 expr(现有函数)给这些高阶函数(是这么称呼吧),返回新的 expr,这样写起来比较直观好玩,当然 stack size 太恐怖,生产环境肯定不会这么用。可能是我最近看 SICP 中毒了吧。

    比如说上面的 parse C++ 注释的,就可以这么写:

    var LineBreak = PEG['/'](PEG['t']('\r\n'), PEG['t']('\n'), PEG['t']('\r'));

    PEG['t'] 接受一个字符串,返回能 consume 这个字符串的 expr。

    var Char = PEG['s'](PEG['!'](LineBreak), PEG['.']);

    PEG['s'] 就是把几个 expr 连在一起,返回值用数组装着,一个失败了,就全部失败。

    var Commnet = PEG['/'](PEG['s'](PEG['t']('//'), PEG['*'](Char), LineBreak), PEG['s'](PEG['t']('/*'), balabala, PEG['t']('*/')));

    好吧我知道不能直接写 // 但是为了省事大家就不要在意了。

    于是乎,回到我的问题,大家知道很多 expr 需要递归:

    CppComment <- // Char* LineBreak
    CCommentHelper "*/" / Char CComentHelper
    CComment <= "/*" CCommentHelper

    var CCommentHelper = ......

    在这种情况下,我的那些高阶函数需要操作一个暂时还不存在的函数,如何是好?虽然可以用七楼的办法,但是那样一点都不优雅……实际上我已经放弃自己写 PEG parser 的打算了,只是保留了一份好奇,有没有办法绕过这个问题?^_^
    14 条回复    1970-01-01 08:00:00 +08:00
    rannnn
        1
    rannnn  
       2014-04-19 12:17:21 +08:00
    没懂
    mikawudi
        2
    mikawudi  
       2014-04-19 12:43:50 +08:00 via Android
    参数先被求值,是指应用序和正则序的问题么?
    jakwings
        3
    jakwings  
       2014-04-19 13:22:38 +08:00
    那个 BIContent 的定义简直是乱来。从纯逻辑上判断是根本不可能存在的。去掉那些杂碎后就好像在说:

    > y = y;
    < ReferenceError: y is not defined

    简直无语。当然,用 var x = x; 是不会有错误提示的,因为有语法糖,相当于 var x = undefined; x = x;
    jakwings
        4
    jakwings  
       2014-04-19 13:27:33 +08:00
    @jakwings Javascript 里面没有 C 语言那样的指针,除非你把 BIContent 赋到某个对象上,参数传的那个对象,还可以临时生成一个函数放上去,否则就别想无中生有了。
    notcome
        5
    notcome  
    OP
       2014-04-19 15:38:33 +08:00
    @mikawudi 是的,我希望生成一个函数调用自身
    notcome
        6
    notcome  
    OP
       2014-04-19 16:05:06 +08:00
    似乎想到一个好的办法

    var self = {value: 'self'};
    var failed = {value: 'failed'};

    function prioritized_choose (choices) {
    function this_expr (input, index) {
    var result = failed;
    for (var i = 0; i < choices.length; i ++) {
    var test;
    if (choices[i] == self)
    test = this_expr(input, index);
    else
    test = choices[i](input, index);
    if (test != failed) {
    result = test;
    break;
    }
    }
    return result;
    }
    }

    嗯, 还没测试, 有语法错误就逗比了……
    但问题是需要调用这个 prioritized_choose(choices) 的是 choices 里的一个 function, 所以这个方法对我来说毫无价值, 呜呜. 求大神支援.
    notcome
        7
    notcome  
    OP
       2014-04-19 16:09:00 +08:00
    哦对可以新建一个对象:
    var func_obj = {};
    func_obj.func = xxxx(xxxx, func_obj);
    func_obj.func(xxxx);

    感觉好不优美……
    xgod
        8
    xgod  
       2014-04-19 19:15:02 +08:00
    function BIGen (token) {
    var BIContent = R['/']([token, R['s']([Char, BIContent])]);
    return R['s']([token, Char, BIContent]);
    }

    function series (first, second) {
    return function (input, index) {
    var result = first(input, index);
    if (result) return result;
    result = second(input, index);
    return {output: result.output, index: result.index};
    }
    }

    ------------
    真心不明白你想描述的问题是什么。你想返回一个闭包,然后在闭包函数里面引用闭包函数本身?
    是这意思?
    function series (first, second) {
    var noname= function (input, index) {
    var result = first(input, index);
    if (result) return result;
    result = noname(input, index);
    return {output: result.output, index: result.index};
    }
    return noname;
    }
    xgod
        9
    xgod  
       2014-04-19 19:18:23 +08:00
    如果没有明确的思路,不建义把代码写得这么绕,估计你想要的是一种类似延时执行的代码绑定操作。
    如果是解析个语法解析器,写一个状态机模式,想好状态变化,写出来的性能与可读性也不会差。
    jakwings
        10
    jakwings  
       2014-04-19 19:44:44 +08:00
    假如你的想法就是 7 楼那样的话,下面的实现够了:
    https://gist.github.com/anonymous/a9aac2e6f96b53f1f1ac
    Golevka
        11
    Golevka  
       2014-04-20 01:49:50 +08:00
    我感觉只要按照传统的stream的方法做就行了。你之所以会感觉难受是因为stream函数的类型是没办法直接写出来的
    bolasblack
        12
    bolasblack  
       2014-04-22 06:56:37 +08:00
    @notcome 实话说那段代码没看懂,不过如果只是要函数自己要调用自己的话,其实是可以使用 arguments.callee 这个东西的
    easing
        13
    easing  
       2014-04-22 12:47:42 +08:00   ❤️ 1
    你要做的貌似是一个简化版的parser combinator, 但你这composite函数的方式不太对。Parser和做parse的函数应该分开构建,你用一个高阶函数做参数本身来定义这个高阶。。。
    notcome
        14
    notcome  
    OP
       2014-04-22 16:36:44 +08:00
    @easing 虽然不太懂您说的分开是指什么……但是没关系我照着 parser combinator 搜就可以了,学习中,谢谢!
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   4063 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 10:13 · PVG 18:13 · LAX 03:13 · JFK 06:13
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.