2013年5月19日日曜日

[Ruby][Scheme] Micro Schemeの実装(10) cond式の評価

cond式を使えるようにした。
https://github.com/takeisa/uschemer/tree/v0.09

cond式の評価

(cond ([p1] [e2])
      ([p2] [e2])
      ...
      ([pn] [en])
      (else [e-else])


条件式をp1→pnの順に評価する。
条件式pnが真の場合、対応する式enを結果とする。
いずれの条件式も成立しなかった場合、elseに対応するen-elseを結果とする。
elseは省略可。
else省略時に条件が一つも成立しなかった場合は例外を出すようにしておこう。

対応するコードは以下の通り。

def eval_cond(exp, env)
  pred_exp_list = cond_to_pre_exp_list(exp)
  pred_exp_list.each do |pred_exp|
    pred, exp = pred_exp
    if pred == :else || eval(pred, env) then
      return eval(exp, env)
    end
  end
  raise "cond: not match conditions"
end

def cond_to_pre_exp_list(exp)
  exp[1..-1]
end




abs関数

condの使用例として、数値の絶対値を求めるabs関数を定義する。

(define (abs x)
  (cond ((< x 0) (- x))
        ((= x 0) 0)
        (else x)))


(- x)に対応していなかったので、引数が一つの場合に、符号を反転できるように、「-」関数を以下のように修正した。

引数が一つの場合
  (- 10) ; => -10
引数が複数の場合
  (- 1 2 3) ;=> -4

「-」関数のコードは以下のとおり。
  :- => [:built_in, lambda {|*x| if x.size == 1 then -x[0] else x.reduce {|a, x| a - x}}]

reduceの初期値を0にすれば、引数の個数の判定処理は不要なので、
  :- => [:built_in, lambda {|*x| x.reduce(0) {|a, x| a - x}}]
でOK。
2013/5/20 全然OKじゃないことに気が付いた。後の方法では、(- 2 1) は -3 になってしまう。


実行結果

(define (abs x)
  (cond ((< x 0) (- x))
        ((= x 0) 0)
        (else x)))
 ;=> [:abs,
 [:closure,
  [:x],
  [:cond, [[:<, :x, 0], [:-, :x]], [[:"=", :x, 0], 0], [:else, :x]],
  [{:abs=>[...]},
   {:true=>true, :false=>false},
   {:+=>
     [:built_in,
      #<Proc:0x85e051c@/home/satoshi/workspace/uschemer/uschemer.rb:10 (lambda)>],
    ..snip..
    :>==>
     [:built_in,
      #<Proc:0x85e03dc@/home/satoshi/workspace/uschemer/uschemer.rb:18 (lambda)>]}]]]

(abs -10)
 ;=> 10

(abs 0)
 ;=> 0

(abs 10)
 ;=> 10


if は condを使ったマクロで実装したい。
その前に、リスト処理をする関数がいくつか必要だ。