2017-12-27 179 views
1

所以我想用JavaScript實現LISP的一個子集。我被困在與lambda相關的兩件事上。如何在JavaScript中實現lambda/anonymous函數

如何實現創建lambda的能力,並同時給它的參數,並立即評估?例如:

((lambda(x)(* x 2)) 3) 

現在我硬編碼這個功能在我的eval環這樣的:

else if (isArray(expr)){ 
     if (expr[0][0] === 'lambda' || expr[0][0] === 'string') { 
     console.log("This is a special lambda"); 
     var lambdaFunc = evaluate(expr[0], env) 
     var lambdaArgs = []; 
     for(var i = 1; i < expr.length; i++){ 
      lambdaArgs.push(expr[i]); 
     }   
     return lambdaFunc.apply(this, lambdaArgs); 
     } 

現在這個工作,如果我寫在參數上面拉姆達它會評估到6,但是,我想知道是否有更聰明的方式來實現這一點?

如果一個lambda改爲綁定到一個符號,例如:

(define fib (lambda(n) 
    (if (< n 2) 1 
    (+ (fib (- n 1))(fib (- n 2))) 
    ))) 

在這種情況下,(定義FIB)部分將由EVAL-環路第一被評​​估,正如如果FIB只是被分配一個號碼:

else if (expr[0] === 'define') { // (define var value) 
     console.log(expr + " is a define statement"); 
     var newVar = expr[1]; 
     var newVal = evaluate(expr[2], env); 
     env.add(newVar, newVal); 
     return env; 
    } 

而λ-功能正在創建這樣的:

else if (expr[0] === 'lambda') { // (lambda args body) 
     console.log(expr + " is a lambda statement"); 
     var args = expr[1]; 
     var body = expr[2]; 
     return createLambda(args, body, env); 
    } 

獨立的函數來創建拉姆達:

function createLambda(args, body, env){ // lambda args body 
    function runLambda(){ 
     var lambdaEnvironment = new environment(env, "lambda environment"); 
     for (var i = 0; i < arguments.length; i++){ 
      lambdaEnvironment.add(args[i], evaluate(arguments[i], env)); 
     } 
     return evaluate(body, lambdaEnvironment); 
    } 
    return runLambda; 
} 

此工作正常的lambda如:

(define range (lambda (a b) 
    (if (= a b) (quote()) 
    (cons a (range (+ a 1) b))))) 

(define fact (lambda (n) 
    (if (<= n 1) 1 
    (* n (fact (- n 1)))))) 

例如,(範圍0 10)返回從0到10

列表但是,如果我在lambda內嘗試lambda,它不起作用。例如:

(define twice (lambda (x) (* 2 x))) 
(define repeat (lambda (f) (lambda (x) (f (f x))))) 

我希望下面的返回40:

((repeat twice) 10) 

但是,相反,它返回一個列表看起來像這樣:

function runLambda(){ var lambdaEnvironment = new environment(env, "lambda 
environment"); for (var i = 0; i < arguments.length; i++){ 
lambdaEnvironment.add(args[i], evaluate(arguments[i], env)); } return 
evaluate(body, lambdaEnvironment); },10 

任何想法可能會丟失這裏?

完整的JavaScript;

//functions for parsing invoice String 

function parse(exp) { 
    return readFromTokes(tokenize(exp));//code 
} 


function isNumeric(arg){ 
    return !isNaN(arg); 
} 

function isArray(obj){ 
    return !!obj && obj.constructor === Array; 
} 

function readFromTokes(exp){ 
    //Create abstract syntax tree 
    if (exp.length == 0) { 
    } 
    var token = exp.shift(); 
    if (token == '('){ 
     var L = []; 
     while (exp[0] != ')') { 
     L.push(readFromTokes(exp)); 
     }   
     exp.shift(); //remove end paranthesis 
     return L; 
    } else { 
     if (token == ')') { 
      console.log("Unexpected)"); 
     } else { 
      return atom(token); 
     } 
    } 
} 

function tokenize(exp){ 
    //Convert a program in form of a string into an array (list) 
    var re = /\(/g; 
    var re2 = /\)/g; 
    exp = exp.replace(re, " ("); 
    exp = exp.replace(re2, ") "); 
    exp = exp.replace(/\s+/g, ' '); 
    exp = exp.trim().split(" "); 
    return exp; 
} 

function atom(exp){ 
    if (isNumeric(exp)) { 
    return parseInt(exp); //A number is a number 
    } else { 
     return exp; //Everything else is a symbol 
    } 
} 

function environment(parentEnvironment, name){ 
    var bindings = []; 
    var parent = parentEnvironment; 
    var name = name; 

    function add(variable, value){ 
     console.log("variable: " + variable + " value: " + value); 
     bindings.push([variable, value]); 
    } 

    function printName(){ 
     console.log(name); 
    } 

    function print() { 
     console.log("printing environment: ") 
     for (var i = 0; i < bindings.length; i++){ 
      console.log(bindings[i][0] + " " + bindings[i][1]); 
     } 
    } 

    function get(variable){ 
     for (var i = 0; i < bindings.length; i++){ 
      if (variable == bindings[i][0]){ 
       return bindings[i][1]; 
      } 
     } 
     if (parent != null){ 
      return parent.get(variable); 
     } else { 
     console.log("No such variable"); 
      return false; 
     } 
    } 

    function getParent(){ 
     return parent; 
    } 

    this.add = add; 
    this.get = get; 
    this.getParent = getParent; 
    this.print = print; 
    this.printName = printName; 
    return this; 
} 

function addPrimitives(env){ 
    env.add("+", function() {var s = 0; for (var i = 0; i<arguments.length;i++){ s += arguments[i];} return s}); 
    env.add("-", function() {var s = arguments[0]; for (var i = 1; i<arguments.length;i++){ s -= arguments[i];} return s}); 
    env.add("*", function() {var s = 1; for (var i = 0; i<arguments.length;i++){ s *= arguments[i];} return s}); 
    env.add("/", function(x, y) { return x/y }); 
    env.add("false", false); 
    env.add("true", true); 
    env.add(">", function(x, y){ return (x > y) }); 
    env.add("<", function(x, y){ return (x < y) }); 
    env.add("=", function(x, y){ return (x === y)}); 
    env.add(">=", function(x, y){ if (x >= y){return true;} else {return false;}}); 
    env.add("<=", function(x, y){ if (x <= y){return true;} else {return false;}}); 
    env.add("eq?", function() {var s = arguments[0]; var t = true; for(var i = 1; i<arguments.length; i++){ if (arguments[i] != s) {t = false }} return t;}); 
    env.add("cons", function(x, y) { var temp = [x]; return temp.concat(y); }); 
    env.add("car", function(x) { return x[0]; }); 
    env.add("cdr", function(x) { return x.slice(1); }); 
    env.add("list", function() { return Array.prototype.slice.call(arguments); }); 
    env.add("list?", function(x) {return isArray(x); }); 
    env.add("null", null); 
    env.add("null?", function (x) { return (!x || x.length === 0); }); 
} 


function createLambda(args, body, env){ // lambda args body 
    function runLambda(){ 
    var lambdaEnvironment = new environment(env, "lambda environment"); 
     for (var i = 0; i < arguments.length; i++){ 
      lambdaEnvironment.add(args[i], evaluate(arguments[i], env)); 
     } 
     return evaluate(body, lambdaEnvironment); 
    } 
    return runLambda; 
} 

function evaluate(expr, env) { 
    console.log(expr + " has entered evaluate loop"); 
    if (typeof expr === 'string') { 
     console.log(expr + " is a symbol"); 
     return env.get(expr); 
    } else if (typeof expr === 'number') { 
     console.log(expr + " is a number"); 
     return expr; 
    } else if (expr[0] === 'define') { // (define var value) 
     console.log(expr + " is a define statement"); 
     var newVar = expr[1]; 
     var newVal = evaluate(expr[2], env); 
     env.add(newVar, newVal); 
     return env; 
    } else if (expr[0] === 'lambda') { // (lambda args body) 
     console.log(expr + " is a lambda statement"); 
     var args = expr[1]; 
     var body = expr[2]; 
     return createLambda(args, body, env); 
    } else if (expr[0] === 'quote') { 
     return expr[1]; 
    } else if (expr[0] === 'cond'){ 
     console.log(expr + " is a conditional"); 
     for (var i = 1; i < expr.length; i++){ 
      var temp = expr[i]; 
      if (evaluate(temp[0], env)) { 
       console.log(temp[0] + " is evaluated as true"); 
       return evaluate(temp[1], env); 
      } 
     } 
     console.log("no case was evaluated as true"); 
     return; 
    } else if (expr[0] === 'if') { 
     console.log(expr + "is an if case"); 
     return function(test, caseyes, caseno, env){ 
      if (test) { 
       return evaluate(caseyes, env); 
      } else { 
       return evaluate(caseno, env); 
      } 
     }(evaluate(expr[1], env), expr[2], expr[3], env); 
    } else if (typeof expr[0] === 'string'){ 
     console.log(expr + " is a function call"); 
     var lispFunc = env.get(expr[0]); 
     var lispFuncArgs = [];   
     for(var i = 1; i < expr.length; i++){ 
     lispFuncArgs.push(evaluate(expr[i], env)); 
     } 
     return lispFunc.apply(this, lispFuncArgs); 
    } else if (isArray(expr)){ 
     if (expr[0][0] === 'lambda' || expr[0][0] === 'string') { 
      console.log("This is a special lambda"); 
      var lambdaFunc = evaluate(expr[0], env) 
      var lambdaArgs= []; 
      for(var i = 1; i < expr.length; i++){ 
       lambdaArgs.push(expr[i]); 
      }   
      return lambdaFunc.apply(this, lambdaArgs); 
     } else { 
      console.log(expr + " is a list"); 
      var evaluatedList = []; 
      for(var i = 0; i < expr.length; i++){ 
       evaluatedList.push(evaluate(expr[i], env)); 
      } 
      return evaluatedList; 
     } 
    } else { 
     console.log(expr + " cannot be interpreted"); 
    } 
} 


var globalEnvironment = new environment(null, "Global"); 

addPrimitives(globalEnvironment); 

function start(string) { 
    return evaluate(parse(string), globalEnvironment); 
} 

var output = function (string) { 
    try { 
     document.getElementById('debugdiv').innerHTML = start(string); 
    } catch (e) { 
     document.getElementById('debugdiv').innerHTML = e.name + ': ' + e.message; 
    } 
}; 

Full HTML;

<html> 
    <head> 
     <meta content="text/html; charset=UTF-8" http-equiv="Content-Script-Type" content="text/javascript"> 
     <title>LISP in JavaScript</title> 
     <script type="text/javascript" src="lisp.js"></script> 
    </head> 
    <body> 
     <form id="repl" name="repl" action="parse(prompt.value)"> 
     lisp==&gt; 
      <input id="prompt" size="200" value="" name="prompt" maxlength="512"> 
      <br> 
      <input type=button style="width:60px;height:30px" name="btnEval" value="eval" onclick="output(prompt.value)"> 
      <br> 
      </form> 
     <div id="debugdiv" style="background-color:orange;width=100px;height=20px"> 
     </div>   
    </body> 
</html> 

進一步的想法,建議和意見當然也是受歡迎的。 謝謝!

+3

'(X => X * 2)(3)'我敢打賭,它更容易transpile&'eval'它... –

+1

這個問題是非常接近到「太寬泛」,有點接近「幫助我的代碼不工作」。請考慮將其調整到提出問題所需的最低限度。 – bhspencer

回答

2

您不檢查操作數的結構以查看它是否爲lambda您操作數evaleval的標準方法是檢查它是否爲原始類型,然後檢查特殊形式和宏,然後在應用之前評估運算符和操作數。

只需刪除其中expr[0][0] === 'lambda' || expr[0][0] === 'string'是真實的一部分,而不是隻返回形式的評估需要apply操作數:

else if (isArray(expr)){ 
    const fn = evaluate(expr[0], env); 
    const evaluatedList = []; 
    for(var i = 1; i < expr.length; i++){ 
    evaluatedList.push(evaluate(expr[i], env)); 
    } 
    return fn(...evaluatedList); 
} 

createLambda是錯誤的,因爲你正在評估中的參數錯誤的環境。因爲參數已經評價這將是正確的:

function createLambda(args, body, env){ // lambda args body 
    function runLambda(){ 
    const lambdaEnvironment = new environment(env, "lambda environment"); 
    for (let i = 0; i < arguments.length; i++){ 
     lambdaEnvironment.add(args[i], arguments[i]); 
    } 
    return evaluate(body, lambdaEnvironment); 
    } 
    return runLambda; 
}