在Haskell考慮以下代碼:解釋器 - 閉包如何捕獲它的名字?
let factorial n = if n < 2 then 1 else n * factorial (n-1) in factorial 3
我看到這個解釋在這樣的順序評估程序:
- 這是一個結合。首先評估定義並評估「in」之後的部分。
- 這是一個定義。評估身體,然後將身體與名稱關聯起來。
- 這是一個lambda。捕捉環境,關閉並返回。
- 定義的主體被評估,現在寫入名字。
- 評估定義,評估表達式的正確部分。
- 評估表達式,返回結果。
我看到這個模型的下列問題:在第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,[在JavaScript(http://www.ecma-international.org/ecma-262/7.0/index。 html#sec-let-and-const-declarations-runtime-semantics-evaluation)它爲'factorial'創建一個未初始化的綁定,然後評估右側(它實際上並不使用*綁定,因爲函數是隻是被定義,不能運行),然後用結果值初始化綁定。所以,是否存在綁定(初始化)當初始評估(這麼早引用錯誤可以被處理,但JavaScript並沒有做到這一點),且具有值運行該功能時的。 FWIW。 –
爲了評估遞歸匿名函數(lambda),您需要一些名爲Y-Combinator的東西。您可能會發現此鏈接有用,http://kestas.kuliukas.com/YCombinatorExplained/和http://stackoverflow.com/questions/93526/what-is-ay-combinator – zeronone
@zeronone要使用y您已經需要一個解釋。另外,一旦我們有了解釋器,與使用遞歸綁定相比,Y的效率非常低。 –