読者です 読者をやめる 読者になる 読者になる

@uents blog

Code wins arguments.

SICP 読書ノート#49 - RubyでSchemeインタプリタをつくろう(8) - 環境に対する操作 (pp.213-228)

SICP Scheme Ruby

環境および束縛、代入まわりを実装しました。やってみると意外と簡単だった。

> (define a 1)
=> nil
> a
=> 1

> (define y 1)
=> nil
> (set! y 2)
=> nil
> y
=> 2

> (set! z 3)
"set_variable_value!: unbound variable; z"

> (define foo (lambda (x) "foo"))
=> nil
> (foo 1)
=> "foo"

ソースコードGitHubに置いています。

環境に対する操作

SICPを参考にEnvironmentクラスを実装。変数と値のペアはHashで持たせる。SICPのコードよりはずっとシンプルにできたと思う。

extend_envronment()はローカル環境を生成するために新しいインスタンスを返す必要があたりに気づけずにしばらくバグっていました。。

class Environment
  def initialize(frames)
    @frames = frames
  end

  def lookup_variable_value(var)
    @frames.each do |frame|
      return frame[var] if frame[var] != nil
    end
    raise "lookup_variable_value : unbound variable " + var.to_s
  end

  def extend_environment(vars, values)
    begin
      return Environment.new([make_frame(vars, values)] + @frames)
    rescue
      raise "extend_envronment: arguments error; " +
            vars.to_s + " " + values.to_s
    end
  end

  def define_variable!(var, value)
    @frames[0][var] = value
  end

  def set_variable_value!(var, value)
    @frames.each do |frame|
      return frame[var] = value if frame[var] != nil
    end
    raise "set_variable_value! : unbound variable " + var.to_s
  end

  private
  def make_frame(vars, values)
    vars.zip(values).to_h
  end
end

変数の評価

Variableオブジェクトを評価する際は環境から変数を探す。

  class Variable
    attr_reader :name

    def initialize(name)
      @name = name
    end

    def eval(env)
      env.lookup_variable_value(@name)
    end
  end

束縛と代入

Assignment、Definitionのevalをそれぞれ実装。

  class Assignment < Base
    def eval(env)
      env.set_variable_value!(@variable.name, @value.eval(env))
      nil
    end
  end

  class Definition < Base
    def eval(env)
      env.define_variable!(@variable.name, @value.eval(env))
      nil
    end
  end

関数適用とローカル環境

関数適用の際に引数と対応する値からローカルな環境を生成する。

  class Procedure < Base
    def apply(arguments)
      env = @env.extend_environment(@params.map { |param| param.name },
                                    arguments) # ローカル環境を生成
      self.eval_sequence(@body, env)
    end
  end

処理系の基盤部分は結構できてきたと思う。次はprimitive proceduresをやります。


※「SICP読書ノート」の目次はこちら