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(); } } }
解决方法
我在这里找到了另一个解决方案: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); } }
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。