どうもこんにちは。
半年前に受けて落ちた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開発とは直接的な繋がりは少ないが、かなり大事な要素を含んでいる気がしたのでもう一回勉強してみようと思います。
以上
