【问题标题】:Interpreter - How closure captures its name?解释器 - 闭包如何捕捉它的名字?
【发布时间】:2017-04-17 01:06:20
【问题描述】:

考虑一下 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. 计算表达式,返回结果。

我发现此模型存在以下问题:在第 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();
    }
  }
}

【问题讨论】:

  • FWIW, in JavaScript 它为factorial 创建一个未初始化的绑定,然后计算右侧(实际上并没有使用绑定,因为函数是只是被定义,而不是运行),然后用结果值初始化绑定。因此,在评估初始化程序时绑定存在(未初始化)(因此可以处理早期引用错误,尽管 JavaScript 不这样做),并且在函数运行时具有值。 FWIW。
  • 为了评估递归匿名函数(lambdas),你需要一个叫做 Y-Combinator 的东西。您可能会发现此链接很有帮助,kestas.kuliukas.com/YCombinatorExplainedstackoverflow.com/questions/93526/what-is-a-y-combinator
  • @zeronone 要使用 Y,您已经需要一个解释器。此外,一旦我们有了解释器,与仅使用递归绑定相比,Y 的效率非常低。
  • @AndrásKovács 我认为 zeronone 建议在预定义环境 const std = { ... , 'Y' : Yimpl } 中添加 Y 作为预定义运算符,其中 Yimpl 可以在 JavaScript 中递归定义,我猜应该不会太低效.
  • @chi 我不确定我是否可以在宿主语言中实现,因为宿主语言不知道我的语言环境。

标签: javascript haskell interpreter language-design


【解决方案1】:

更新

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

不过,这种语言的 Evaluator 有所不同。 基本上,归结为:

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

【讨论】:

    猜你喜欢
    • 2014-01-11
    • 1970-01-01
    • 2013-07-29
    • 1970-01-01
    • 1970-01-01
    • 2020-08-16
    • 1970-01-01
    • 1970-01-01
    • 2014-09-07
    相关资源
    最近更新 更多