REx模擬試験誤答一覧 その2

70点まで点数を伸ばした... あと12日間全力で頑張れば...という希望が見えてきた。

問4: Rubyの実行オプション

Rubyの実行オプションは教科書に載っているもので以下の通り、

  • -v (version)
  • -h (help)
  • -c (check syntax, dry run)
  • -e (execute, ワンライナー実行)
  • -w (warning mode, 警告の表示 )
  • -W0, -W1, -W2, -W (警告レベルの設定)
  • -d (debug mode)
  • -r (require the library, スクリプトの実行前に指定したファイルをあらかじめ実行)
  • -l (load file,詳細後述)

-l オプション

  • l オプションはrequireやloadが行われたときに、相対パスで指定されたファイルを探すときの基準ディレクトリを指定するもので、デフォルトでは 1: Rubyをインストールしたフォルダ 2: Rubyを実行したフォルダ から順に探す。

この1よりも更に優先順位の高いルードディレクトリの設定方法として、組み込み定数$LOAD_PATHを指定する方法がある。この設定を-lオプションで設定できる。 更にこの方法よりも一つ優先順位が下がるが、RUBYLIBという環境変数を実行環境に設定しておく方法もある。

優先順位を整理すると

($LOAD_PATH) > (RUBYLIB) > Rubyのインストールフォルダ > Rubyの実行フォルダ

の順になる。なおこれで設定されているディレクトリの参照は$:でも確認できる。 また、ライブラリ関係の問題として(問47:)requireのロードはコード中に何度書かれていたとしても、一回しか行われない。

教科書には載っていないが

  • -n
  • -p
  • -a

当たりも覚えておいた方がよさそう。

pオプション

オプションを付けて実行したファイルをwhile文でループさせる。

p $_.split("")

これをオプションで実行すると

pyons@LAPTOP-SF87NLCB:/mnt/c/Users/broad/OneDrive/products/Ruby認定技術者試験Gold$ ruby -p options.rb
hello
["h", "e", "l", "l", "o", "\n"]
hello
world
["w", "o", "r", "l", "d", "\n"]
world

となる。 nオプションは特殊変数$_が標準出力されない。

pyons@LAPTOP-SF87NLCB:/mnt/c/Users/broad/OneDrive/products/Ruby認定技術者試験Gold$ ruby -n options.rb
Hello
["H", "e", "l", "l", "o", "\n"]
World
["W", "o", "r", "l", "d", "\n"]

最後にaオプションはaやpオプションと併用し、入力内容を$Fという特殊変数に格納する。

p $_.split("")
puts "This is #{$F}"
pyons@LAPTOP-SF87NLCB:/mnt/c/Users/broad/OneDrive/products/Ruby認定技術者試験Gold$ ruby -ap options.rb
hello
["h", "e", "l", "l", "o", "\n"]
This is ["hello"]
hello
World
["W", "o", "r", "l", "d", "\n"]
This is ["World"]
World
!!!
["!", "!", "!", "\n"]
This is ["!!!"]

問10: const_gets

定数の探索について少し霧が晴れたかもしれない。 const_getと通常の定数呼び出しでは定数探索のルートが全く異なる。

class Human
   NAME = "Unknown"
 
  def self.name
     # NAME
     const_get(:NAME)
   end
 end
 
 class Fukuzawa < Human
   NAME = "Yukichi"
 end
 
 puts Fukuzawa.name # Youkichi
class Human
   NAME = "Unknown"
 
  def self.name
     NAME
     # const_get(:NAME)
   end
 end
 
 class Fukuzawa < Human
   NAME = "Yukichi"
 end
 
 puts Fukuzawa.name  # unknown

通常のインスタンス変数探索なら、親クラスのメソッドが実行されたとしても、selfは子クラスのインスタンスのままなので、子クラスのインスタンス変数が呼び出される。 const_getはこれに似ていて、まずはselfに所属する定数から探し始め、なければ定数そのものをメソッドと同じように上クラスまでさかのぼって探す。実際、Fukuzawaクラスから定数を削除するとUnknownを返した。

一方通常の定数探索は、レキシカルスコープという、「記述された通りのスコープ」を辿って、それでも見つからなければ「自分とスーパークラス」つまり上のクラスだけを探しに行く。

Rubyの定数探索の個人的な謎に迫る - Qiita

実際、子クラスの定数を無視するのにレキシカルスコープを辿ってトップレベルの定数は参照出来る、ということが起こる。

なお、スーパークラス参照であっても、再オープンした分に関して参照できる。(下記参照)

class Animal
end

class Human < Animal
 
  def self.name
     NAME
     #const_get(:NAME)
   end
 end
 
 class Fukuzawa < Human
   NAME = "Yukichi"
 end

 class Animal
  NAME = "re-define class"
 end
 
 puts Fukuzawa.name # re-define class

問17: Enumerable module と Enumerator class

enum_char = Enumerator.new do |yielder|
  "apple".each_char do |chr|
    __(1)__
  end
end

array = enum_char.map do |chr|
  chr.ord
end

p array

Enumuratorのことがよくわかっていないと解けない。

ぢみへんプログラミング日誌 Enumerator がなんで必要なのかようやく分かった

chenck, laze,each_with_indexなどのメソッドはEnumerableモジュールがincludeされているクラスの各オブジェクトにeachメソッドを呼ぶことで実現している。つまりeachメソッドがないオブジェクトにはEnumerableモジュールがincludeされてもそれらのメソッドが利用できない。

ならeachメソッドをクラスに定義してやればEnumerableからメソッドを呼ぶことができるのだろうか。

class University
  include Enumerable

  def initialize 
    @students = ['tatsuya','haruya','kouhei','motohiro','jyunichi']
  end

  def each 
    @students.each do |student|
      yield student
    end
  end
end

University.new.each_with_index do |student, i|
  puts "#{i} : #{student}"
end

# 0 : tatsuya
# 1 : haruya
# 2 : kouhei
# 3 : motohiro
# 4 : jyunichi

できたできた。

ただし、この方法だとEnumerableモジュールのメソッドが欲しくなってから、モジュールのincludeとeachメソッド(イテレータと呼ぶ)のオーバーラップを行わなくてはならない。eachというメソッドをオーバーライドするのは、クラス内全体に影響する変更なので、あんまりやりたくない。クラスの中身を出来るだけ変えず実行するにはどうするか。

という悩みをEnumerator classがうまくやってくれる。外から使用するイテレータを明示してやれば、それをeachメソッドの代わりに使ってEnumerableモジュールのincludeもやってくれますよ、というもの。

class University
  # include Enumerable

  def initialize 
    @students = ['tatsuya','haruya','kouhei','motohiro','jyunichi']
  end

  def student_each 
    @students.each do |student|
      yield student
    end
  end
end

obj = University.new
obj_with_enum = Enumerator.new(obj, :student_each)
obj_with_enum.each_with_index do |student, i|
  puts "#{i} : #{student}"
end

# options.rb:16: warning: Enumerator.new without a block is deprecated; use Object#to_enum
# 0 : tatsuya
# 1 : haruya
# 2 : kouhei
# 3 : motohiro
# 4 : jyunichi

できるけど、2.1.0でも警告出る。そして、この警告からわかるようにto_enumenum_forメソッドはObjectクラスのメソッドで、あらゆるオブジェクトに上記と同じことを実行してくれる。

class University
  # include Enumerable

  def initialize 
    @students = ['tatsuya','haruya','kouhei','motohiro','jyunichi']
  end

  def student_each 
    @students.each do |student|
      yield student
    end
  end
end

obj = University.new
# obj.to_enum(:stundet_each) 非破壊的メソッドだよ!
obj.to_enum(:student_each).each_with_index do |student, i|
  puts "#{i} : #{student}"
end

# 0 : tatsuya
# 1 : haruya
# 2 : kouhei
# 3 : motohiro
# 4 : jyunichi

ここまで理解したところで一度問題に戻る。

enum_char = Enumerator.new do |yielder|
  "apple".each_char do |chr|
    __(1)__
  end
end

Enumeratorクラスは本来、イテレータの存在するオブジェクトと、そのイテレータを引数に指定して初期化するオブジェクトであるが、今回の場合、引数はブロックだけ渡している。ブロック内部ではeach文が定義されている。

このオブジェクト(yielder object)に対して、each_with_indexとかを実行すると内部で定義したeach文とともに、indexも一緒に実行される。

enum_char = Enumerator.new do |yielder|
  ['A','B','C'].each do |integer|
    yielder << integer
  end
end

enum_char.each_with_index do |n, i|
  p "#{n}:#{i}"
end
 "A:0"
 "B:1"# "C:2"

おそらく、ブロックで渡した中身のeach文はEnumerator.newの引数で指定するイテレータのことなのだろう。普段はメソッド名をシンボルで指定するけど、今回はわざとブロックで渡してイテレータを直接定義したわけ。

唯一、イテレータのメソッド名で直接指定するのと違うのは、<<メソッドで、そのイテレータの戻り値をちゃんと指定してやる必要があるということ。<<の左側は外側のブロックの実引数。

先ほどのUniversityクラスで、ブロックにイテレータを指定すると、こんな感じになる。挙動はさっきのメソッド名を指定したときと同じ。

@students = ['tatsuya','haruya','kouhei','motohiro','jyunichi']

enum_char = Enumerator.new do |yielder|
  @students.each do |student|
    yielder << student
  end
end

enum_char.each_with_index do |n, i|
  p "#{i}:#{n}"
end

最後に問題のコード全体を見てみる。

enum_char = Enumerator.new do |yielder|
  "apple".each_char do |chr|
    yiledr << chr
  end
end

array = enum_char.map do |chr|
  chr.ord
end

p array

この問題では、enum_charオブジェクトのイテレータに、each_charを指定したようだ。なので、このオブジェクトにEnumerableモジュールのメソッドを実行すると、イテレータの戻り値として各1文字が渡ってくる。

戻ってきた各位置文字を数字化して、代入しているので数字が5個入った配列が返却される。

参考: singleton method Enumerator.new (Ruby 2.6.0)

問26: クラス変数のスコープ

クラス変数の探索はレキシカルに行われる。 以前、特異クラスで定義したクラス変数もクラスから参照することが出来るということを書いたが、これは特異クラスがレキシカルスコープ上で行われているから、というのが理由なようだ。

class C
  @@val2 = 10
  class << C  
    p @@val2 # 10を表示(レキシカルスコープだから)
  end
end

class B
  class << B 
    @@val = 18
  end
  p @@val # 18を表示
end

module M
  @@val = 20

  class << C
    p @@val  # 20をリターン
    p @@val2 # 参照できない!
  end
end

変数と定数 (Ruby 2.6.0)

問29: トップクラスに定数を定義

トップクラスに定義した定数はObjectクラスに定義してあることになるらしい。 Ruby のトップレベルメソッドって結局なんなの - Qiita

そして、トップレベルで定義したメソッドはObjectクラスのprivateメソッドとして定義されるらしい。

全てのクラスはObjectクラスを継承しているので、定数探索のルールに基づきレキシカル→自分+スーパークラスの順にたどっていく。 メソッドconst_defined?は引数にfalseを指定しない限り、継承先の定数まで見に行くので、最終的にはObjectクラスの定数にもたどり着く。

問30: 特殊な演算記号と優先順位

v1 = 1 / 2 == 0
v2 = !!v1 or raise RuntimeError
puts v2 and false

一行目はFixnum同士の計算なので、除算の結果は0となり、等価なのでtrueが入る。

2行目はv1の前についている!!はnot演算子なのでtrueに対して2回適用することで、再び左辺にtrueが入る。 2行目のorは左辺が真である以上、そのまま左辺の値を返すので、例外は発生しない。

3行目はv2にtrueが入っているが、and演算子は優先順位が =と比べて低いので、左辺の値がそのままはいってしまう。 よって3行目にはtrueが出力される。

なお、3行目の演算子&&であった場合、優先順位が=と比べて強いので左辺も実行される。

問33: Enumeratorオブジェクト

eachメソッドが定義されたクラスに対して、incldueを行うことで、様々な便利メソッドを提供するenumerableモジュールを再オープンして定義しなおしている。

定義しなおした後は、元からenumerableモジュールがincludeされて使用可能な配列に対して、reverse_eachメソッドを実行しており、"Awesome"というprefixをつけて実行したいらしい。

to_enumの本来の使い方では、最初の引数はEnumerableモジュールの恩恵を受けたいオブジェクトのイテレータとなるメソッドである。 今回は引数として2つを指定する使い方で、第一引数にはやはり、イテレータとなるメソッドを指定する。

instance method Object#enum_for (Ruby 2.6.0)

第二引数には、呼び出すイテレータに渡す引数を指定し、これにprefixを渡している。 (以下、編集中)

module Enumerable
  def with_prefix(prefix)
    return to_enum(__(1)__, prefix) { size } unless block_given?

    each do |char|
      yield "#{prefix} #{char}"
    end
  end
end

[1,2,3,4,5].with_prefix("Awesome").reverse_each {|char|
  puts char
}

問39: クラスを指定しない例外発生と補足

まず例外クラスの継承関係は Exception > Standard Error > Runtime Errorとなっている。 Syntax ErrorはExceptionからScript Errorを継承したところにあるので、継承関係の全く異なる位置にいる。

一方他のほとんどのエラーName ErrorArgument Errorを含めてRuntime Errorと同じ親を持っている。

raiseで例外クラスを指定しなかった場合、発生するのは Runtime Errorである。また、捕捉する例外のクラスを指定しないrescueStandard Errorとなる。両者は捕捉する方が、発生した例外の親クラスとなっているので、捕捉可能である。また、自分で定義した例外型に関しても多くの場合 Runtime Error から継承させるので、Standard Error 全般を捕捉できる rescue 節なら救出を行うことが出来る。

(標準的なエラーはすべて Standard Errorの子であることを抑えておこう。)

問40: 親クラスのメソッドにsuperする場合

もし、子クラスの superが引数をとっていた場合、親クラスのsuperで引数を用いなかったとしても、super()としなければならない。引数を実際に渡す必要はない。 また、この場合でも親クラスのinitializeに仮引数を指定しておく必要がない。

class Parent
  def initialize
    puts "initialize in parent"
  end

  def method
    puts "method in parent"
  end
end

class Child < Parent
  def initialize(a,b,c)
    super()
  end

  def method(e,f,g)
    super
  end
end

Child.new(1,2,3) # options.rb:2:in `initialize': wrong number of arguments (3 for 0) (ArgumentError)
Child.new(1,2,3).method(4,5,6) # options.rb:6:in `method': wrong number of arguments (3 for 0) (ArgumentError

initializeに限らず、子クラスのメソッドでは引数を必要としていても、親クラスでは不要とされている場合はどんなメソッドにも適用されるルールである。

問44: module.instance_eval

モジュールに特異クラスメソッドを定義する方法は * module_functionを使う * モジュールの特異クラスをオープンする * module.instance_evalを使う

実際2番目と3番目の両方の再オープンで同じ特異クラスがひらけているのがわかる。

m = Module.new

class << m 
  puts Module.nesting # #<Class:#<Module:0x007fffbe7f7130>>
end

m.instance_eval(<<-EOS)
  CONST = "Constant in Module instance"
  puts Module.nesting # #<Class:#<Module:0x007fffbe7f7130>>

  def const
    CONST
  end
EOS

puts m.methods(false) # const

最後の方法はクラスに対して行っても同じである。

class Test
  puts Module.nesting # Test
end

class << Test
  puts Module.nesting # #<Class:Test>
end

Test.instance_eval(<<-EOS)
  CONST = "Constant in Module instance"
  puts Module.nesting # #<Class:Test>

  def const
    CONST
  end
EOS

puts Test.methods(false) # const

instance_evalはレシーバーをオブジェクトに設定し、オブジェクトの特異クラスをカレントクラスにするというのが一般的な挙動である。

それをクラスに対して行ったら、当然クラスをオブジェクトをとらえ、クラスの特異クラスを定義する。 なので、この例えは特異クラスの定義と特異メソッドの定義に成功している。

問45: extend self

extend selfはモジュールにクラスメソッドを定義する方法として利用可能である。

問46: 特異クラスのスコープ内でのself

メタプログラミングRubyの最初のselfの解説にもあったように、メソッド定義内でのselfはメソッドを呼び出したオブジェクトそのものである。 一方クラス定義内のselfはクラスそのものが返る。

class C
end

class << C
  puts self #<Class:C>
  def _singleton
    self # C
  end
end

p C._singleton