μSchemeR の Ruby による実装を読む(その3 - クロージャ)

: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

すばらしい! 確かに「環境」が保持されています。これこそがクロージャです。


obelisk.hatenablog.com
obelisk.hatenablog.com