:pぴー.sendせんど(:pぴー, :pぴー)

mokoaki
mokoriso@gmail.com

2017/07/22

Gold模擬問題を解いた時に気になったポイントメモ

Ruby技術者認定試験の書籍写経メモ

Module class
Class class
nil (´・ω・`)
BasicObject class
#BasicObject singleton_class
Kernel class (module) Object include Kernel
#Kernel singleton_class
Object class
#Object singleton_class
InclM class (module) Oyaji include InclM
#InclM singleton_class
Oyaji class
#Oyaji singleton_class
PrepM class (module) Oyaji prepend PrepM
#PrepM singleton_class
Moko class
#Moko singleton_class
moko instance
#moko singleton_class
#Module singleton_class
#Class singleton_class
ExteM class (module) Oyaji extend ExteM
superclass
メソッド探索ルート
superclass
superclass
superclass
superclass
superclass
superclass
superclass
superclass
superclass
無名クラス
module InclM end module PrepM end module ExteM end class Oyaji include InclM prepend PrepM extend ExteM end class Moko < Oyaji end moko = Moko.new
Ruby メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
メソッド探索ルート
レシーバ.NoMethodError (´・ω・`)
メソッド探索ルート
superclass
メソッド探索ルート
メソッド探索ルート
superclass
メソッド探索ルート
メソッド探索ルート
レシーバが Object なら こっちルート
レシーバが(ry
レシーバが(ry
レシーバが(ry
無名クラス
無名クラス
無名クラスの特異クラス 通常ならアクセスしない / できない?
無名クラス
レシーバが Oyaji なら こっちルート
レシーバが Moko なら こっちルート
無名クラスの特異クラス 通常ならアクセスしない / できない?
無名クラスの特異クラス 通常ならアクセスしない / できない?

口癖

スコープ

あまり使われないのに出題される組み込み定数

ARGF(ARGVじゃないぞ)

「スクリプトに指定した引数 (Object::ARGV を参照) をファイル名とみなして、 それらのファイルを連結した 1 つの仮想ファイルを表すオブジェクト」

./moko1.txt

moko1 1
moko1 2

./moko2.txt

moko2 1
moko2 2
# irbではダメ
p ARGF.readlines
$ ruby temp.rb ./moko1.txt ./moko2.txt
#=> ["moko1 1\n", "moko1 2\n", "moko2 1\n", "moko2 2\n"]

DATA

__END__ 以降は実行対象にならず、Data(Fileオブジェクト)からアクセスされるデータと成り果てる

# irbではダメ
p DATA.readlines
__END__
a
b
c

# => ["a\n", "b\n", "c\n"]

コマンドラインオプション

手で打たないと覚えないよ

-I(アイ)

$ ruby -e 'p $:'
$ ruby -I mokohage -e "p $:"

-c 文法チェック

$ ruby -c -e "moko("
syntax error

-e ワンライナー

$ ruby -e "p [1, 2, 3].sum"
6

-W0 -W1 -W2(W) 冗長出力モード 右ほど冗長 -w1がデフォルト

$ ruby -e "HAGE = HAGE = 1"
-e:1: warning: already initialized constant HAGE
-e:1: warning: previous definition of HAGE was here

$ ruby -W1 -e "HAGE = HAGE = 1"
-e:1: warning: already initialized constant HAGE
-e:1: warning: previous definition of HAGE was here

$ ruby -W0 -e "HAGE = HAGE = 1"
=> ワーニングが出ない

-r スクリプト実行前に指定されたファイルをrequireする

moko1.rb がrequireされた後に6が表示される

$ ruby -e "p [1, 2, 3].sum" -r ./moko1

-d デバックモード

スレッドの例外はデバッグモードで顕現するのを観測してみる

$ ruby -e "Thread.new { raise 'HAGE'}"
$ ruby -d -e "Thread.new { raise 'HAGE'}; sleep 100"

require, load

require 1度しか読み込まない 拡張子の補完を行う バイナリエクステンションが読み込める
load 何度でも読み込む 拡張子の補完を行わない バイナリエクステンションは読み込めない

可変長引数 多重代入

基本形

a, b = 1, 2, 3

a
=> 1
b
=> 2

配列でもおk

a, b = [1, 2, 3]

a
=> 1
b
=> 2

*を付けて渡すと「これ配列だけど、分解して送るわ!」みたいな感じ(つまり、何も考えずに使うと基本形と同じ動作ぽくなる)

a, b = *[1, 2, 3]

a
=> 1
b
=> 2

例えば2つ目の配列を分解して送りたい!時に使える

a, b = 1, *[2, 3]

a
=> 1
b
=> 2

*を付けて受け取ると「分解して送られてきてるけど、配列として残り全部配列で貰うわ!」みたいな感じ

a, *b = 1, 2, 3

a
# => 1
b
# => [2, 3]

*を付けて受け取って、さらに「配列として残りぜん・・あ、最後の引数は貰い先が決まってるぽいから残り全部配列で貰うわ!」みたいな感じ

a, *b, c = 1, 2, 3, 4

a
# => 1
b
# => [2, 3]
c
# => 4
a, *b, c = 1, 2, 3

a
# => 1
b
# => [2]
c
# => 3

注意

代入時とメソッド時で感覚的な挙動が違ったりする

*argv = [1, 2, 3]

p argv
# => [1, 2, 3]
def hage(*argv)
  p argv
end

hage([1, 2, 3])
# => [[1, 2, 3]]

def hage(*argv)
  p argv
end

hage(1, 2, 3)
# => [1, 2, 3]

キーワード引数

基本形

def moko(a:, b:)
  p a, b
end

moko(a: 1, b: 2)
# => 1
# => 2

ハッシュでもおk

def moko(a:, b:)
  p a, b
end

moko({a: 1, b: 2})
# => 1
# => 2

引数を勝手に省略する事は許されない

def moko(a:, b:, c:)
  p a, b
end

moko(a: 1, b: 2)
# => ArgumentError: missing keyword: c

初期値を設定すれば省略できる

def moko(a:, b:, c: 3)
  p a, b, c
end

moko(a: 1, b: 2)
# => 1
# => 2
# => 3

moko(a: 1, b: 2, c: 100)
# => 1
# => 2
# => 100

どんなキーワードが来るか知らんが、残りは全て受け取ってやる!という気概

def moko(a:, b:, **keywords)
  p a, b, keywords
end

moko(a: 1, b: 2, c: 3, d: 4)
# => 1
# => 2
# => {:c=>3, :d=>4}

引数にハッシュが渡されている場合、何も考えないともちろんエラー

def moko(a:, b:)
  p a, b
end

moko(a: 1, { b: 2 })
# => SyntaxError 渡せない、というよりは構文としておかしいと判断される

引数にハッシュが渡されている場合「ハッシュを展開して下さい!ほらキーワード引数なんですよ!」と構文解釈させる感じ

def moko(a:, b:)
  p a, b
end

moko(a: 1, **{ b: 2 })
# => 1
# => 2

何も考えずに使うと基本形と同じ動作ぽくなる

def moko(a:, b:)
  p a, b
end

moko(**{ a: 1,  b: 2 })
# => 1
# => 2

Numerable連中の四則演算の結果

的に変換される Floatは自身に精度がない為にもう一辺の精度を犯すが、Complexだけは他では表現するのは無理なので特別・・みたいな感じ

例外

定義できないメソッド名

::
=(+=, =+, ..等の自己代入演算子)
? :
$で始まるグローバル変数名
他にはえーと・・
def def
  'ok'
end

p send(:def)
=> "ok"
def end
  'ok'
end

p send(:end)
=> "ok"
def true
  'ok'
end

send(:true)
=> "ok"
def and
  'ok'
end

send(:and)
=> "ok"
def if
  'ok'
end

send(:if)
=> "ok"
def return
  'ok'
end

send(:return)
=> "ok"
def self
  'ok'
end

send(:self)
=> "ok"
def send
  'ok'
end

self.send # さすがにsendを潰したのでsendメソッドは使えない
=> "ok"
def ENV
  'ok'
end

send(:ENV)
=> "ok"
def __ENCODING__
  'ok'
end

send(:__ENCODING__)
=> "ok"
define_method(:"'") { 'ok' }

send(:"'")
=> "ok"
define_method(:"&&") { '&& ok' }

send(:"&&")
=> "&& ok"
define_method(:"||") { '|| ok' }

send(:"||")
=> "|| ok"

定数

module A
  Value = 'A'
  module B
    p Value

    module C
      Value = 'C'
    end

    p Value
  end
end
# => "A"
# => "A"

内側(ネストの深い方)を探しに行ってはくれない(ただし、定数名のみで指定した場合)

module A
  module B
    p Value
    # => NameError: uninitialized constant A::B::Value

    module C
      Value = 'C'
    end

    p Value
    # => NameError: uninitialized constant A::B::Value
  end
end
# => "A"
# => "A"

::を使って定数を参照した場合は、外側(ネストの浅い方)へ(相対位置を)探しにいく ある意味、内側(ネストの深い方)も見てくれる、とも言えなくもない

module A1
  module B1
    Value = 'ok'
  end

  module B2
    p B1::Value
  end
end
# => "OK"

prepend していてもそのクラスから定数を見つける

module M
  Value = 'M'
end

class Moko
  Value = 'Moko'

  prepend M

  p Value
  # => "Moko"
end

p Moko::Value
# => "Moko"

const_missing

Proc.new, Kernel#proc, Kernel#lambdaを指に覚えさせておく

Proc.new { |a| p a }.call(6)
proc { |a| p a }.call(6)
lambda { |a| p a }.call(6)
-> (a) { p a }.call(6)

catch, throw の動作

catch のブロック内全てを実行したいが・・throw の時点で catch ブロック内の動作を中断する・・ようなイメージ

catch(:homo) do
  p 1
  p 2
  throw(:homo)
  p 3
end
1
2
=> nil

throw の第2引数が戻り値

catch(:homo) do
  p 1
  p 2
  throw(:homo, 'HOMO')
  p 3
end
1
2
=> "HOMO"

throwするのがメソッドでもよい。スタックを駆け上る

def homo
  throw(:homo, 'HOMO')
end

catch(:homo) do
  p 1
  p 2
  homo
  p 3
end
1
2
=> "HOMO"

catch, throwのラベルは省略できない

dup, clone 出題される

clone 汚染状態、インスタンス変数、ファイナライザ、凍結状態、特異メソッド を複製する
dup 汚染状態、インスタンス変数、ファイナライザ を複製する

dupでは特異メソッドはコピーされない

moko = []

def moko.hage
  'HAGE-!'
end

p moko.hage
# => "HAGE-!"
p moko.clone.hage
# => "HAGE-!"
p moko.dup.hage
# => undefined method 'hage'

dupでは凍結状態ははコピーされない(最近のRubyだと凍結状態のコピーが指定できたりする)

moko = []
moko.freeze

p moko.frozen?
# => true
p moko.clone.frozen?
# => true
p moko.dup.frozen?
# => false

ファイナライザはそのオブジェクトが開放される時に呼ばれる処理を定義できる・・らしい dupでもコピーされる

a = []

ObjectSpace.define_finalizer(a) {
  puts 'I m dead'
}

b = a.dup
c = a.clone

GC.start
# => "I m dead"
# => "I m dead"
# => "I m dead"

Thread

thread を開始するのは 3つ!コレ出題されるんで注意

ねう!ふぉーく!すたあと!と唱える

Thread.new Threadクラスを継承したサブクラスを開始する時にinitializeを呼ぶ
Thread.fork Threadクラスを継承したサブクラスを開始する時にinitializeを呼ばない
Thread.start Threadクラスを継承したサブクラスを開始する時にinitializeを呼ばない

指で覚える

  Thread.new { p 'thread' }
  Thread.form { p 'thread' }
  Thread.start { p 'thread' }

スレッドの中で例外が起こったらどうなるか

Module.append_features, Module.included

module M
  class << self
    def append_features(klass)
      p "before include in #{klass}"
    end

    def included(klass)
      p "after include in #{klass}"
    end
  end
end

include M
# => "before include in Object"
# => "after include in Object"

Enumerable#lazy (Enumerable::lazy) のお話

#force 遅延評価を開始する(実は#to_aと同じ)
#take(n) アタマからn個が対象だよ、と言うが遅延評価状態のままlazyオブジェクトを返す
#first(n) アタマからn個が対象だよ、と言って遅延評価を開始する

正規表現

最短マッチというものがある、ちょっと小耳に挟んでおく

正規表現は全力で最長マッチするように働く

url = 'http://example.com/moko/hage/'
/^http:\/\/example.com\/(.*)\// === url

$1
# => "moko/hage"

のを、「全力は出さなくていいよ、条件に合う中で一番短いマッチをしてくれればいいよ」なのが最短マッチである

# ? が1つ追加されてる

url = 'http://example.com/moko/hage/'
/^http:\/\/example.com\/(.*?)\// === url

$1
# => "moko"

もう一つ例

'_12_3456_' =~ /\d{3,}/

p $&
# => 3456
'_12_3456_' =~ /\d{3,}?/

p $&
# => 345

Fiber

覚えるのはコレだけ!

Fiber.new {}
Fiber.yield
y.resume
f = Fiber.new do
  p 'これが表示されたという事は、初回の resume が実行されたという事'
  Fiber.yield('戻り値 1')

  p 'これが表示されたという事は、再度 resume が実行されたという事 まぁ、実行されないんですけどね'
  Fiber.yield('戻り値 2')

  'I am end'
end

p 'START'

sleep 1

p '一息ついてコレが表示される'

p f.resume

#=> "START"
#=> "一息ついて Fiber.yield の戻り値が表示される。これが表示されたという事は、初回の resume が実行されたという事"
#=> "戻り値 1"

IOのクラスツリー

UDPServer は存在しない。そんなもの ウチには ないよ
StringIO は IO のようなインターフェースを持つが、IO のサブクラスではない

Time, Date, DateTime

四則演算

require 'date'

datetime1 = DateTime.now
sleep 1
datetime2 = DateTime.now

p (datetime2 - datetime1).to_f * 86400
=> 1.002833

Date, DateTime で出題される気がするメソッド

- 1 前の日
+ 1 次の日
« 1 前の月
» 1 次の月

ちなみに

Time.now + 1 1秒後
Time.now « 1 エラー

何度でも 特殊変数 おさらい

$0, $1, $2… $_ は出題される気がする

$0 実行中のプログラムファイル名
   
$1 マッチした1番目のキャプチャ
$2.. マッチした2番目のキャプチャ..
$+ マッチした最後のキャプチャ ($~.to_a.last == $+) => true
$~ MatchDataオブジェクト 配列っぽくアクセスできる ($~[0] => マッチ箇所 $~[1] => キャプチャ1)
   
$` マッチした部分より前の文字列
$& マッチした部分 ($~.to_a.first == $&) => true
$’ マッチした部分より後の文字列
   
$_ 最後にgetsやreadlineで読み込んだ文字列
   
$: $LOAD_PATH
   
$* ARGV
   
$? 最後に終了した子プロセス
$! 直近で補足した例外オブジェクト
$@ バックトレース

ちなみにネストしたキャプチャは始まった順番に重複して取得可能

/(\d(\d(\d)(\d))\d)/ === '12345'
#=> true

p $1 # => "12345"
p $2 # => "234"
p $3 # => "3"
p $4 # => "4"

def Module#define_method

class Moko
  define_method :hage do
    'hage-!'
  end
end

p Moko.new.hage
=> "hage-!"

alias Module#alias_method

class Moko
  def moko
    'moko'
  end

  alias :old_moko :moko
end

Moko.new.old_moko
# => "moko"
class Moko
  def moko
    'moko'
  end

  alias old_moko moko

  def moko
    'new_moko'
  end
end

Moko.new.old_moko
# => "moko"
Moko.new.moko
# => "new_moko"
class Moko
  def moko
    'moko'
  end

  alias_method 'old_moko', 'moko'

  def moko
    'new_moko'
  end
end

p Moko.new.moko

undef Module#undef_method

class A
  def a
    'A'
  end
end

class B < A
  def a
    'B'
  end

  undef_method :a
end

p B.new.a
# => undefined method 'a'

Module#remove_method

class A
  def a
    'A'
  end
end

class B < A
  def a
    'B'
  end

  remove_method :a
end

p B.new.a
# => "A"

クラス変数

あーやべえ ルールわかんねえ

class Moko1
  p @@kurasu = 1

  class << self
    p @@kurasu += 1
  end

  def insmethod
    p @@kurasu += 1
  end
end

class Moko2 < Moko1
  p @@kurasu += 1
end

moko1 = Moko1.new
moko2 = Moko2.new

moko1.insmethod
moko2.insmethod

def moko1.tokui_method
  p @@kurasu += 1
end

moko1.tokui_method
# =>NameError: uninitialized class variable @@kurasu in Object

sort が出題される

[1, 3, 2].sort { |a, b| a <=> b }
# => [1, 2, 3]

[1, 3, 2].sort { |a, b| a - b }
# => [1, 2, 3]

おとなしく sort_by 使ってくれればいいのに、いじわるでsortが出題される

[1, 3, 2].sort_by { |a| a }
# => [1, 2, 3]

YAML

やむるはろーど!だんぷ!

require 'yaml'

yaml_string = <<YAML_STRING
moko:
  - hoge1
  - hoge2
  - hoge3:
    - hoge3-1
    - hoge3-2
YAML_STRING

yaml_data = YAML.load(yaml_string)

p yaml_data
# => {"moko"=>["homo1", "homo2"]}

p YAML.dump(yaml_data)
# => "---\nmoko:\n- homo1\n- homo2\n"

基本的なルール

とりあえずハッシュと配列があればほぼイケる

ハッシュ

yaml_string = <<-YAML_STRING
moko: hoge
YAML_STRING

YAML.load(yaml_string)
# => {"moko"=>"hoge"}

配列

yaml_string = <<-YAML_STRING
- hoge1
- hoge2
- hoge3
YAML_STRING

YAML.load(yaml_string)
=> ["hoge1", "hoge2", "hoge3"]

組み合わせ

yaml_string = <<-YAML_STRING
moko:
  - hoge1
  - hoge2
  - hoge3:
    - hoge3-1
    - hoge3-2
YAML_STRING

YAML.load(yaml_string)
=> {"moko"=>["hoge1", "hoge2", {"hoge3"=>["hoge3-1", "hoge3-2"]}]}

JSON

じぇいそんはろーど!ぱーす!だんぷ!

require 'json'

json_string = <<-JSON_STRING
{"homo": true, "homos": ["a", "b"]}
JSON_STRING

p json_object = JSON.load(json_string) # => {"homo"=>true, "homos"=>["a", "b"]}
p JSON.parse(json_string) # => {"homo"=>true, "homos"=>["a", "b"]}

p JSON.dump(json_object) # => "{\"homo\":true,\"homos\":[\"a\",\"b\"]}"

p ({moko: [1, 2, 3]}.to_json) # => "{\"moko\":[1,2,3]}"
p ([1,2,3, {moko: 1}].to_json) # => "[1,2,3,{\"moko\":1}]"

基本的なルール

とりあえずハッシュと配列があればほぼイケる

ハッシュ

json_string = <<-JSON_STRING
{"moko1": "hoge"}
JSON_STRING

JSON.load(json_string)
# => {"moko1"=>"hoge"}

配列

json_string = <<-JSON_STRING
[
  "moko1",
  "moko2",
  "moko3"
]
JSON_STRING

JSON.load(json_string)
# => ["moko1", "moko2", "moko3"]

組み合わせ

json_string = <<-JSON_STRING
{
  "moko1":[
    "hoge1",
    "hoge2",
    {
      "hoge3": [
        "hoge3-1",
        "hoge3-2"
      ]
    }
  ]
}
JSON_STRING

JSON.load(json_string)
# => {"moko1"=>["hoge1", "hoge2", {"hoge3"=>["hoge3-1", "hoge3-2"]}]}

Object#respond_to? のおはなし

ancestorsも含めて検索対象である (通常のメソッド呼び出しと同じ感覚)

class Oyaji
  def hoge
  end
end

class Moko < Oyaji
end

p Moko.new.respond_to?(:hoge)
# => true

undef, remove すると false (通常のメソッド呼び出しと同じ感覚)

class Moko
  def hage1
  end

  def hage2
  end
end

p Moko.new.respond_to?(:hage1)
# => true

class Moko
  undef_method :hage1
  remove_method :hage2
end

p Moko.new.respond_to?(:hage1)
# => false
p Moko.new.respond_to?(:hage2)
# => false

定数の探索経路をおさらい

通常、自分のクラスで定数が見つからなければ親クラスに探しに行く

class Oyaji
  CONST = 'OYA_CONST'
end

class Moko < Oyaji
  CONST = 'C_CONST'

  def hage
    CONST
  end
end

p Moko.new.hage
# => "C_CONST"
class Oyaji
  CONST = 'OYA_CONST'
end

class Moko < Oyaji
  def hage
    CONST
  end
end

p Moko.new.hage
# => "OYA_CONST"

include はselfの直後に追加(後から処理した奴が優先されるという事)

prepend はselfの末端に追加(後から処理した奴が優先されるという事)

extend はselfの特異クラスの末端追加(後から処理した奴が優先されるという事)

include, prepend したModule内のメソッド内に定数が定義してあると取り込んだクラスの定数になる

extend は関係なし ancestorsルート関係ないしって事か

module II
  I = true
end

module PP
  P = true
end

module EE
  E = true
end

class Moko
  include II
  prepend PP
  extend EE
end

Moko::I
# => true

Moko::P
# => true

Moko::E
# => NameError: uninitialized constant Moko::E
module IncleM
  def incle1
    CONST
  end
end

module PrepenM
  def prepen1
    CONST
  end
end

module ExtenM
  def exten1
    CONST
  end
end

class Moko
  CONST = 'C_CONST'

  include IncleM
  prepend PrepenM
  extend ExtenM
end

moko = Moko.new

moko.prepen1
# => NameError: uninitialized constant PrepenM::CONST
moko.incle1
# => NameError: uninitialized constant IncleM::CONST
Moko.exten1
# => NameError: uninitialized constant ExtenM::CONST

superメソッドがスーパークラスのメソッドを見つける時

普通に継承したクラスでのsuperを使用

class Moko1
  def hage
    p 'Moko1#hage'
  end
end

class Moko2 < Moko1
   def hage
     p 'Moko2#hage'
    super
  end
end

m2 = Moko2.new

m2.hage
# => "Moko2#hage"
# => "Moko1#hage"

サブクラスのメソッド名を変更してみると・・

class Moko1
  def hage
    p 'Moko1#hage'
  end
end

class Moko2 < Moko1
   def hage
     p 'Moko2#hage'
    super
  end

  alias_method :hage2, :hage
  remove_method :hage
end

m2 = Moko2.new

m2.hage
# => "Moko1#hage"

m2.hage2
# => "Moko2#hage"
# => "Moko1#hage"
class Moko1
  def hage
    p 'Moko1#hage'
  end
end

class Moko2 < Moko1
   def hage2
    p 'Moko2#hage'
    super
  end

  alias_method :hage, :hage2
  remove_method :hage2
end

m2 = Moko2.new

m2.hage
# => "Moko2#hage"
# => super: no superclass method 'hage2'
class Moko1
  def hage2
    p 'Moko1#hage2'
  end

  alias_method :hage, :hage2
  remove_method :hage2
end

class Moko2 < Moko1
   def hage
    p 'Moko2#hage'
    super
  end
end

m2 = Moko2.new

m2.hage
# => "Moko2#hage"
# => "Moko1#hage2"

exit とは

「そこでプログラムを終了する」ではなく SystemExit < Exception 例外を発生させる

begin
  exit
rescue SystemExit => e
  p e
  p e.class
  p e.class.superclass
end

# => #<SystemExit: exit>
# => SystemExit
# => Exception

ちなみに exit! は例外も発生させずにマジ終了する

begin
  exit!
rescue Exception => e
  p e
  p e.class
  p e.class.superclass
end

全ての例外を捉えるはずの Exception であっても rescue できずに終了する

トップレベルのメソッドを定義するということ

RDOCの書式が出題される

書けば覚えるだろうけど・・

標準添付ライブラリ#RDoc

Marshal.dump で文字列化できないオブジェクト

Marshal.dump

クラスやモジュールをfreezeするとどうなる

まずは、できるのか? => できます

class A
end

module M
end

A.freeze
# => A

M.freeze
# => M