REx模擬試験誤答一覧 その6と7

第六回目は88点 第七回目は86点でした。

1回目 (12/7実施)

問17: Refinement

Refinement#usingはメソッドの中では呼び出せない。クラス内だけ。

問18: include時の定数探索

includeが行われたとしても、それはそのクラスのレキシカルスコープに属したことにはならない。 今回のコードは以下のようなものだが、モジュール内のメソッドをインスタンスメソッドとして扱えるようになっても、スコープそのものは変わらないので、例外が発生する。

module M
  def refer_const
    CONST
  end
end

class C
  include M
  CONST = 'Hello World'
end

c = C.new
p c.refer_const

問27: 名前空間の修飾と定義時のネスト関係

名前空間の修飾関係class A::Bと、定義される場所のネスト関係は全く別物である。 なのでModule.nestingで返却される値も異なる。

module A
  module B   
    p Module.nesting # [A::B, A]  
    module C   
      p Module.nesting # [A::B::C, A::B, A]
    end
  end
end

module A::B   
  p Module.nesting # [A::B]
end

定義された場所のネストの深さに応じて配列の長さは変化する。 一番最初に名前空間の修飾関係が来て、その後ろにその外側のネストの名前空間の修飾関係が来る。

問50: newメソッドはどこに定義されている?

newメソッドは、全てのクラスの特異メソッドとして利用でいきている。 継承関係の最上位のクラスの特異クラスとして定義されていれば、そこで定義された特異メソッドは継承関係を通じて下のクラスも利用できる。特異クラスの継承関係の最上位はClass classなので(下図)、少なくともClass classで定義されていると推測できる。

f:id:Pyons:20191209145205j:plain
Rubyの継承関係

このことから最初の2行の結果は推測できる。

puts Class.method_defined? :new 
puts String.method_defined? :new

Classクラスにメソッド定義があるので、一行目はtrue。このクラスは各クラスの特異クラスを通じて継承されているので、2行目はfalse。

puts Class.singleton_class.method_defined? :new
puts String.singleton_class.method_defined? :new

4行目は一般的なクラスの特異クラス(#String class)を指しているので、ここはClass classから継承されたnewが使えると思って間違いなさそう。

3行目はこの継承関係とは全く別に、Class自体にもnewメソッドが必要であるため、classクラスの特異クラスにもnewを定義して使えるようにしている。

第2回 (12/9実施)

問19: instance_eval

選択肢2番目。instance_evalは本来レシーバーをオブジェクトに、オブジェクトに対して新しい特異クラスを生成し、それに対して新メソッドの追加やメソッドのオーバーライドを可能にしている。 今回はモジュールをレシーバーにして使っているため、これによってinstance_evalはモジュールの特異クラスを生成することになる。

ただし、instance_evalmodule_evalと異なり、ブロックを渡しても文字列を渡しても、評価に違いはない。

module_evalの説明 => instance method Module#class_eval (Ruby 1.8.7) instance_evalの説明 => instance method Object#instance_eval (Ruby 1.8.7)

つまり、instance_evalに対して文字列を渡しても、モジュールの定義と同じスコープを開くことにはならない。 よって定数の参照には失敗する。

module M
  p Module.nesting # [M]
  CONST = "Hello, world"
end

class << M  
  p Module.nesting # [#<Class:M>]
end

M.instance_eval(<<-CODE)
  def say
    CONST
  end
  p Module.nesting # [#<Class:M>]
CODE

p M::say # (eval):2:in `say': uninitialized constant Module::CONST (NameError)

そしてそれを裏付けるように選択肢4番目はmodule_evalに文字列を渡していて、文字列でクラスの特異クラスと定数の宣言を行っている。module_evalに文字列を渡すときは(宣言位置的)スコープの変更が行われているので、定数の取得に成功している。

定義済みのモジュールのメソッドや定数に書き手が自分から(宣言位置的)スコープを変更しなくてもアクセスできるようになるのはmodule_eval + 文字列 だけの特例と考えたほうがよさそう。

選択肢1番目は、特異クラスにメソッドを宣言するにあたって、事前にモジュールの再オープンが行われている。そのため、スコープはモジュールに移っているので、モジュールの定数の取得に成功している。

最後に選択肢の3番目は特異クラスの宣言をトップレベルで行おうとしているので、これもスコープの違いから定数の取得に失敗している。

問題21: aliasとalias_methodの違い

aliasとalias_methodは別のメソッドで挙動がことなる。

alias => リテラル(そのまま)かシンボルで指定し、間にコンマは付けない。 alias_method => シンボルか文字列で指定し、間にコンマを付ける。

aliasはキーワードであるのに対してalias_methodは通常のメソッドである。メソッドに対して引数を渡すときは当然ハッシュか文字列になるし(リテラルを渡す文字列などない。)複数の引数を渡すならコンマが必要になる。

問題43: モジュールに対するinstance_eval

これは問題19でも取り扱ったように、モジュールに対するinstance_evalは特異クラスの定義になる。 今回はinstance_evalで渡した文字列内に定数が定義されているので、スコープによる定数参照の問題は起こらない。

問46: attr_メソッド

複数選択の問題で1つ(三番目)しか選択出来ていなかった。 まず、一番最後の選択肢はメソッドのループが発生してしまう。

2番目の選択肢もsuperの指す先(継承関係の上側)に同名のメソッドがないためNo method errorとなる。

最後に一番目の選択肢はattr_readerで設定したインスタンスメソッドのgetterメソッドだけを、alias_method命名変更することに成功している。新しいgetterメソッドの名前はoriginal_nameとなり、そのあとに別のnameメソッドを追加してもオーバーライドがされない。(異なるメソッドとして機能する。)

よって期待通りの挙動をする。以下は疑似コード。

class Men
  attr_reader :name

  alias :name_getter :name

  def name
    "Professor. " + name_getter
  end

  def initialize(name)
    @name = name
  end
end

man = Men.new("mizukami")
puts man.name # Professor. mizukami