解释器 - 闭包如何捕获其名称?

时间:2016-12-02 07:20:19

标签: javascript haskell interpreter language-design

在Haskell中考虑这段代码:

let factorial n = if n < 2 then 1 else n * factorial (n-1) in factorial 3

我看到解释器以这样的顺序评估程序:

  1. 这是一种约束力。首先评估定义并在“in”之后评估该部分。
  2. 这是一个定义。评估身体,然后将身体与名称联系起来。
  3. 这是一个lambda。捕获环境,关闭并返回。
  4. 评估定义的正文,立即将其写入名称。
  5. 评估定义,评估表达式的正确部分。
  6. 评估表达式,返回结果。
  7. 我在这个模型中看到以下问题:在第3步,当闭包捕获环境时,它对“阶乘”绑定一无所知。

    我正在为JavaScript编写类似ML的语言的解释器,我偶然发现了这个问题。例如,以下语言代码:

    fac = \x -> if (== x, 0) { 1 } else { fac (- x, 1) } in fac 3
    
    由于描述的问题,

    将无效。

    其他语言的口译员如何解决这个问题?

    这是参考解释器的代码。

    "use strict";
    
    const grammar =
    `
    Expression "expression"
      = Condition
      / Application
      / Lambda
      / Binding
      / Integer
      / String
      / Identifier
      / '(' expr: Expression ')' { return expr; }
    
    _ "whitespace"
      = [ \\t\\n\\r\\n]*
    
    Integer "integer"
      = [0-9]+ {
        return { type: 'literal',
                 literalType: 'Integer',
                 value: parseInt(text(), 10)
              };
      }
    
    String "string"
     = '\"' value: ([^\"]* { return text(); } ) '\"' {
       return { type: 'literal',
                literalType: 'String',
                value: value
              };
        }
    
    Letter
      = [a-zA-Z]
    
    Identifier
      = (Letter / '+' / '-' / '*' / '/' / '_' / '==' / '>' / '<')+ {
        return {
            type: 'identifier',
            value: text()
        }
      }
    
    Application
      = id: Identifier _ args: ActualArguments {
         return { type: 'application',
                fun: id,
                args: args
              }
      }
      / lambda: ('(' l: Lambda ')' { return l; }) _ args: ActualArguments  {
         return { type: 'application',
                fun: lambda,
                args: args
              }
        }
    
    ActualArguments
     = expr: Expression rest: (',' _ e: Expression { return e })* { return [expr].concat(rest); }
    
    Lambda
      = '\\\\' args: Arguments _ '->' _ body: Expression {
       return { type: 'lambda',
                args: args,
                body: body
              }
        }
    
    Arguments
      = head: Identifier rest: (',' _ i: Identifier { return i; })* { return [head].concat(rest); }
    
    Binding
     = name: Identifier _ '=' _ def: Expression _ 'in' _ expr: Expression {
       return {
         type: 'binding',
         name: name,
         def: def,
         expr: expr
       }
     }
    
    Condition
     = 'if' _ '(' _ cond: Expression _ ')' _ '{' _ expr1: Expression _ '}' expr2: ( _ 'else' _ '{' _ e: Expression _ '}' { return e; })? {
       return {
         type: 'condition',
         condition: cond,
         expr1,
         expr2
       }
     }
    `
    
    const parser = peg.generate(grammar);
    
    const std = {
      '+': (arg1, arg2) => arg1 + arg2,
      '-': (arg1, arg2) => arg1 - arg2,
      '*': (arg1, arg2) => arg1 * arg2,
      '/': (arg1, arg2) => arg1 / arg2,
      'str': (arg1, arg2) => [arg1, arg2].join(""),
      '>': (arg1, arg2) => arg1 > arg2,
      '<': (arg1, arg2) => arg1 < arg2,
      '==': (arg1, arg2) => arg1 === arg2,
      'false': false,
      'true': true,
      'and': (arg1, arg2) => arg1 && arg2,
      'or': (arg1, arg2) => arg1 || arg2
    }
    
    const makeLambda = (fun, parentEnv) => {
      return (...args) => {
          const env = Object.assign({}, parentEnv);
          fun.args.forEach((el, i) => {
            env[el.value] = args[i];
          });
          return _eval(fun.body, env);
      }
    }
    
    const evalLiteral = (literal) => {
      switch (literal.literalType) {
        case 'Integer':
          return parseInt(literal.value);
        case 'String':
          return String(literal.value);
        default:
          console.log('Literal type undefined');
          return literal.value;
      }
    }
    
    const _eval = (expr, parentEnv = std) => {
      const env = Object.assign({}, parentEnv);
      switch (expr.type) {
        case 'application':
          const fun = _eval(expr.fun, env);
          const args = expr.args.map(arg => _eval(arg, env));
          return fun.apply(null, args);
          break;
        case 'literal':
          return evalLiteral(expr);
        case 'identifier':
          return env[expr.value];
        case 'lambda':
          return makeLambda(expr, env);
        case 'binding':
          env[expr.name.value] = _eval(expr.def, env);
          return _eval(expr.expr, env);
        case 'condition':
          if (_eval(expr.condition, env)) {
            return _eval(expr.expr1, env);
          } else {
            return _eval(expr.expr2, env);
          }
      }
    }
    
    const parseAndEval = (str) => {
      try {
        const parsed = parser.parse(str);
        console.log(parsed);
        return _eval(parsed);
      } catch (e) {
        if (e.name == "SyntaxError" ) {
        return e.toString() +
          " start: " + JSON.stringify(e.location.start) +
          " end: " + JSON.stringify(e.location.end);
        } else {
          return e.toString();
        }
      }
    }
    

1 个答案:

答案 0 :(得分:0)

<强>更新

我在这里找到了另一个解决方案:http://lisperator.net/pltut/eval1/new-constructs

评估员对这种语言有些不同。 基本上,它归结为:

function make_lambda(env, exp) {
    if (exp.name) {                    // these
        env = env.extend();            // lines
        env.def(exp.name, lambda);     // are
    }                                  // new
    function lambda() {
        var names = exp.vars;
        var scope = env.extend();
        for (var i = 0; i < names.length; ++i)
            scope.def(names[i], i < arguments.length ? arguments[i] : false);
        return evaluate(exp.body, scope);
    }
    return lambda;
}

我在实施ML编译器的讲座中找到了解决方案: http://www.cs.cornell.edu/courses/cs312/2004fa/lectures/lecture24.htm

“闭包包含唯一参数的名称(见下文),定义函数体的表达式(即抽象语法树),以及对创建闭包的环境的引用。我们使用引用环境有两个原因:

  • 首先,通过存储引用而不是实际环境,我们可以节省空间。

  • 第二 - 更重要的是 - 引用的使用允许创建包含闭包的环境,嵌入的引用指向环境本身。这在定义递归或相互递归函数时很重要。“

所以,我所做的是将环境引用传递给makeLamba()而不是副本。因此,lambda在创建时共享环境,但是为了评估其身体环境的副本(因此lambda中的任何内容都不会改变父环境)。

  // ...
  case 'lambda':
    return makeLambda(expr, parentEnv); // not a copy but a reference
  // ...

  const makeLambda = (fun, parentEnv) => {
    return (...args) => {
        const env = Object.assign({}, parentEnv);
        fun.args.forEach((el, i) => {
          env[el.value] = args[i];
        });
        return _eval(fun.body, env);
    }
  }