どうもこんにちは。
半年前に受けて落ちたRubyGoldをまた受けようかなと思い、久々にRexを使用して模擬問題を受けてみました。
結果は...
70点...(合格点は75点)
解説
自分なりに解説してみました。ご指摘あったらコメントください。
1
Singleton
モジュールは、「あるモジュールのインスタンスがプログラム全体で1つしか存在しないことを保証する」モジュールです。
require 'singleton'
class Message
include Singleton
def morning
'Hi, good morning!'
end
end
p Message.__(1)__.morning ## (1)には「instance」が入ります。
なので、include Singleton
したクラスのインスタンスはnew
で作成できず、instance
メソッドを使用して作成することになります。
2
C.new
でインスタンスが作成されます。
インスタンス内ではインスタンスメソッドしか呼ばれないので、class << self
の部分は実行されないと考えられます。
@val
は「インスタンス変数」呼ばれるものになりますが、クラス内で定義されているため@val
はクラスメソッドでのみ呼ばれます。
attr_accessor :val
では、val
というゲッターメソッドとセッターメソッドが定義されます。ゲッターメソッドとセッターメソッドは、@val
というインスタンス変数を共有します。
※わかりづらいですが、クラス内で定義された@val
とattr_accessorで定義された@val
は別物です。
class C
@val = 3
attr_accessor :val
class << self
@val = 10
end
def initialize
@val *= 2 if val
end
end
c = C.new
c.val += 10
p c.val
以上のことを踏まえると、C.new
で作成されたインスタンスから呼ぶことができるコードの中には@val
に値が代入されているコードはありません。そのため、initialize
メソッドのif文はFalseとなり、c.val += 10
のコードはエラーとなります。
3
succ!
メソッドは数字や文字を1つ進めるメソッドです。
Child1
とChild2
はObject
クラスを継承しています。(継承記号<
が使われていないので分かりづらいですが、気をつけてください。)
以下のコードは1行目から順にRubyインタプリタに読まれていきます。
読み込まれる時には、クラスからアクセスできるコードが呼ばれます。
Object
クラスが読み込まれたときは、CONST
定数に1
が代入されます。(const_succ
メソッドはインスタンスメソッドなので実行されず。)
Child1
クラスが読み込まれた時は、const_succ
メソッド二つが呼ばれます。一つ目のconst_succ
はChild1
クラスの継承元クラスを順に探していき、Object
クラスのconst_succ
が見つかり次第実行されます。二つ目のconst_succ
はclass << self
の中で呼ばれているので、一つ目のconst_succ
と同様に呼ばれます。
Child2
クラスが読み込まれた時は、一つ目のconst_succ
は呼ばれますが、二つ目は呼ばれません。なぜなら、initialize
メソッドはインスタンスメソッドだからです。インスタンスメソッドはRubyインタプリタによってクラスが読み込まれている時は呼ばれません。
Child1.new
が実行された時には、Child1
にインスタンスメソッドは存在しないので何も実行されません。
Child2.new
が実行された時には、initialize
メソッドが実行されて、const_succ
が呼ばれます。
class Object
CONST = "1"
def const_succ
CONST.succ!
end
end
class Child1
const_succ
class << self
const_succ
end
end
class Child2
const_succ
def initialize
const_succ
end
end
Child1.new
Child2.new
p Object::CONST
以上のことから、const_succ
が呼ばれた回数は4回なので、最初のCONST = "1"
と合わせると、"5"になります。
4
以下のコードも上のコードと同じ理屈です。
Rubyインタプリタにクラスが読み込まれた段階では、@@val
の値は1
です。
C.new
が2回呼ばれていますが、ここでは、@@val
の値は更新されません。
S.new
が呼ばれた時は、initialize
メソッドの中で@@val
の値が更新されます。
class S
@@val = 0
def initialize
@@val += 1
end
end
class C < S
class << C
@@val += 1
end
def initialize
end
end
C.new
C.new
S.new
S.new
p C.class_variable_get(:@@val)
以上から@@val
の値は3
になります。
5
refine
は訳わからなくなる...
refine
はメソッドを再定義する時に使います。refine C.singleton_class do
はクラスメソッドを再定義するときに使用します。refine C do
の場合はインスタンスメソッドを再定義するときに使用します。
再定義を適用するには、using R
を使用します。
class C
def self.m1
200
end
end
module R
refine C.singleton_class do
def m1
100
end
end
end
using R
puts C.m1
なので、puts C.m1
の結果は100
になります。
6
&
から始まるブロック引数は、仮引数の中で最後に記述する必要があります。
def hoge(&block, *args)
block.call(*args)
end
hoge(1,2,3,4) do |*args|
p args.length > 0 ? "hello" : args
end
なので、このコードではエラーが出ます。
7
以下のコードでは、do...end
と{...}
の違いによって結果が変わります。
do...end
と{...}
では、{...}
の方が結合度は強いです。
今回は、do...end
でブロックが定義されているため、m2
はm1
の引数として読み込まれてしまいます。Rubyインタプリタによって、メソッドの呼び出し時には先に引数の評価がされます。
そのため、m1
よりもm2
の方が先に呼ばれます。
def m1(*)
str = yield if block_given?
p "m1 #{str}"
end
def m2(*)
str = yield if block_given?
p "m2 #{str}"
end
m1 m2 do
"hello"
end
なので、次のように出力されます。
"m2 "
"m1 hello"
8
以下のコードでは、m1
メソッドは全てインスタンスメソッドです。
最初にK.new.m1
が呼ばれるので、「ポイントA」のsuper value - 100
が呼ばれます。これは、「value - 100
という引数を持った継承元の同名メソッド」を呼び出しています。
先に引数が計算され、その後に継承元の同名メソッド(module M
のm1
メソッド)が呼ばれます。
その後、「ポイントB」のsuperが呼ばれます。ここでのsuperは、using M
が適用される前のm1
メソッドが呼ばれるので、「ポイントC」のメソッドが呼ばれます。
class C
end
module M
refine C do
def m1(value)
super value - 100 # ポイントB
end
end
end
class C
def m1(value)
value - 100 # ポイントC
end
end
using M
class K < C
def m1(value)
super value - 100 # ポイントA
end
end
puts K.new.m1 400
よって、結果は100
となります。
9
ここでの、ポイントは、using M
がクラスの中で定義されていることです。これは、クラスCが呼ばれた時にusing M
が呼ばれるということです。
今回はC.new
で、クラスが呼ばれているのではなく、インスタンスが呼ばれています。そのため、using M
は呼ばれません。
class C
def m1
400
end
end
module M
refine C do
def m1
100
end
end
end
class C
using M
end
puts C.new.m1
したがって、400
が出力されます。
using M
がメソッド内で呼ばれるとエラーになるので注意しましょう。
10
たまに混乱しますが、freeze
メソッドは「オブジェクトの変更を禁止しているのではなく、破壊的なオブジェクトの変更を禁止」しています。
array = ["a", "b", "c"].freeze
array = array.map!{|content| content.succ}
p array
なので、今回はmap!
メソッドが破壊的な動作をするので、例外が発生します。
しかし、map
メソッドであった場合は、例外が発生せずに["b", "c", "d"]
が出力されます。
11
ややこしいですが、freeze
メソッドは「破壊的なオブジェクトの変更を禁止はしていますが、破壊的なオブジェクトの要素の変更していません。」
array = ["a", "b", "c"].freeze
p array.frozen? # true
array.each do |a|
p a.frozen? # 各要素に対して、[false, false, false]となる
a.upcase!
end
配列自体は凍結されていますが、配列の要素は凍結されていません。なので、a.upcase!
ではエラーが発生しません。
一つ前のmap!
メソッドは、array
という凍結された配列に対して実行されているのに対し、今回のupcase!
メソッドは要素に対して実行されています。
12
キーワード引数のついたメソッドを呼び出すときは、key
とvalue
を同時に渡さないとエラーが発生します。
def foo(arg:)
puts arg
end
foo 100 # foo arg: 100 ならOK
13
キーワード引数のついたメソッドを呼び出すときに、ハッシュを渡すこともできます。
ハッシュを渡す時には、ハッシュの格納された変数の先頭に**
をつけます。(*
だと別の意味になるので注意)
def foo(arg1:100, arg2:200)
puts arg1
puts arg2
end
option = {arg2: 900}
foo arg1: 200, **option
14
Fiber
は、「軽量スレッド」を意味します。
Fiber
クラスのresume
メソッドは、Fiber.new
に指定したブロックの最初の評価、またはFiber.yield
をが最後に実行された行から評価を行います。
f = Fiber.new do |total|
Fiber.yield total + 10
end
p f.resume(100) + f.resume(5)
-
f.resume(100)
を実行する -
Fiber.new
のブロックを評価し、引数total
には100
がセットされる -
100 + 10
はFiber.yield
の引数なので、先に計算がされてからFiber.yield
が呼ばれる -
Fiber.yield
が呼ばれた時の結果が、resume
メソッドを実行した時の戻り値になるので、f.resume(100)
の結果は110
となる - 次に
f.resume(5)
を実行する - 前回の続きから処理が始まるが、すでに前回
resume
メソッドを実行した時に処理が終了しているので、f.resume(5)
の戻り値が5になる - 結果的に、
110 + 5
が計算されて115
が出力される
15
以下のコードでは、f.resume
が3回以上実行された場合にエラーが発生します。
Fiber.yield
の数+1の回数分f.resume
が実行できる考えて良いのかもしれません。
f = Fiber.new do
Fiber.yield 15
5
end
16
Date
クラス同士の引き算はRational
クラスになります。
d = Date.today - Date.new(2015,10,1)
p d.class
同じようなオブジェクトであっても引き算された後のクラスが異なるので注意しましょう。
引き算されるクラス | 引き算された後のクラス |
---|---|
Date同士 | Rational |
Time同士 | Float |
DateTime同士 | Rational |
17
この問題は、継承チェーンの順序を正しく理解する必要があります。
以下のコードのinclude
がない場合、クラスCはクラスC2を継承しています。
ここにinclude
が入った場合、クラスCは、クラスC2とモジュールMを継承していることになります。
include
の継承チェーンは、親クラスより手前にモジュールがつながります。
prepend
が使用された場合には、継承したクラスの処理が実行された後に、モジュールの処理が実行されます。
module M
def foo
super
puts "M#foo"
end
end
class C2
def foo
puts "C2#foo"
end
end
class C < C2
def foo
super
puts "C#foo"
end
include M
end
C.new.foo
なので、答えは以下のようになります。
C2#foo
M#foo
C#foo
まとめ
Rails開発とは直接的な繋がりは少ないが、かなり大事な要素を含んでいる気がしたのでもう一回勉強してみようと思います。
以上