3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【備忘録】久しぶりにRubyGoldの勉強をしてみた

Last updated at Posted at 2024-04-11

どうもこんにちは。

半年前に受けて落ちたRubyGoldをまた受けようかなと思い、久々にRexを使用して模擬問題を受けてみました。

結果は...

70点...(合格点は75点)

スクリーンショット 2024-04-04 23.04.30.png

解説

自分なりに解説してみました。ご指摘あったらコメントください。

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つ進めるメソッドです。

Child1Child2Objectクラスを継承しています。(継承記号<が使われていないので分かりづらいですが、気をつけてください。)

以下のコードは1行目から順にRubyインタプリタに読まれていきます。
読み込まれる時には、クラスからアクセスできるコードが呼ばれます。
Objectクラスが読み込まれたときは、CONST定数に1が代入されます。(const_succメソッドはインスタンスメソッドなので実行されず。)
Child1クラスが読み込まれた時は、const_succメソッド二つが呼ばれます。一つ目のconst_succChild1クラスの継承元クラスを順に探していき、Objectクラスのconst_succが見つかり次第実行されます。二つ目のconst_succclass << 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でブロックが定義されているため、m2m1の引数として読み込まれてしまいます。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 Mm1メソッド)が呼ばれます。
その後、「ポイント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

キーワード引数のついたメソッドを呼び出すときは、keyvalueを同時に渡さないとエラーが発生します。

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)
  1. f.resume(100)を実行する
  2. Fiber.newのブロックを評価し、引数totalには100がセットされる
  3. 100 + 10Fiber.yieldの引数なので、先に計算がされてからFiber.yieldが呼ばれる
  4. Fiber.yieldが呼ばれた時の結果が、resumeメソッドを実行した時の戻り値になるので、f.resume(100)の結果は110となる
  5. 次にf.resume(5)を実行する
  6. 前回の続きから処理が始まるが、すでに前回resumeメソッドを実行した時に処理が終了しているので、f.resume(5)の戻り値が5になる
  7. 結果的に、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開発とは直接的な繋がりは少ないが、かなり大事な要素を含んでいる気がしたのでもう一回勉強してみようと思います。

以上

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?