:lambda にクロージャを実装する前に、let文を実装します。
[:let, [[:x, 3], [:y, 2]], [:+, :x, :y]]
は
[[:lambda, [:x, :y], [:+, :x, :y]], 3, 2]
と同等です。
実装。単純です。:lambda に変換しているだけです。
def eval_let(exp, env) parameters, args, body = let_to_parameters_args_body(exp) new_exp = [[:lambda, parameters, body]] + args _eval(new_exp, env) end def let_to_parameters_args_body(exp) [exp[1].map {|e| e[0]}, exp[1].map {|e| e[1]}, exp[2]] end def let?(exp) exp[0] == :let end
既存のメソッドも多少変更して let文に対応します。
def special_form?(exp) lambda?(exp) or let?(exp) end def eval_special_form(exp, env) if lambda?(exp) eval_lambda(exp, env) elsif let?(exp) eval_let(exp, env) end end
実際に使ってみます。
puts ([:let, [[:x, 3], [:y, 2]], [:+, :x, :y]], [$primitive_fun_env]) #=>5
OK です。
さて、クロージャというなら、以下の文が動かなくてはなりません。
[:let, [[:x, 2]], [:let, [[:fun, [:lambda, [], :x]]], [:let, [[:x, 1]], [:fun]]]]
そしてさらに、これが 2 を返さなければいけません。:let は:lambda を作るので lambda_apply() で「環境」を作ります。ただし、このままでは NoMethodError が出ます。:lambda の「パラメータ」が空リストになっているからです。
lookup_var() を以下のごとく変更します。こうすると :lambda の「パラメータ」がなくても :let で作った「環境」を参照することができます。
def lookup_var(exp, env) alist = env.find{|alist| alist.key?(exp)} raise "couldn't find value to variables:'#{exp}'" unless alist alist[exp] end
実際に確かめてみます。
exp = [:let, [[:x, 2]], [:let, [[:fun, [:lambda, [], :x]]], [:let, [[:x, 1]], [:fun]]]] puts _eval(exp, [$primitive_fun_env]) #=>2
確かに 2 が返ります。
さらなる例。
exp = [:let, [[:x, 3]], [:let, [[:fun, [:lambda, [:y], [:+, :x, :y]]]], [:+, [:fun, 1], [:fun, 2]]]] puts _eval(exp, [$primitive_fun_env]) #=>9
すばらしい! 確かに「環境」が保持されています。これこそがクロージャです。