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
を返した。
一方通常の定数探索は、レキシカルスコープという、「記述された通りのスコープ」を辿って、それでも見つからなければ「自分とスーパークラス」つまり上のクラスだけを探しに行く。
実際、子クラスの定数を無視するのにレキシカルスコープを辿ってトップレベルの定数は参照出来る、ということが起こる。
なお、スーパークラス参照であっても、再オープンした分に関して参照できる。(下記参照)
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_enum
やenum_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
問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 Error
や Argument Error
を含めてRuntime Errorと同じ親を持っている。
raiseで例外クラスを指定しなかった場合、発生するのは Runtime Error
である。また、捕捉する例外のクラスを指定しないrescue
はStandard 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