Slide 1

Slide 1 text

ワクワク! Rubyクイズ!! 〜初級編〜
 2021/06/30 Nakaya Ryota

Slide 2

Slide 2 text

自己紹介
 ギフティ入社:2019/01
 所属:ContentsCreation Div. ProductUnit2(コーヒー屋さんチーム) 
 社内部活:#coffee、#darts、#poker、#among_us... 
 前職:バックオフィス系システムのパッケージベンダー(上流メイン) 
 好きな現場猫(仕事猫):ストゼロ現場猫 
 分報:#times_nakaya


Slide 3

Slide 3 text

技術スタック


Slide 4

Slide 4 text

みなさん毎日元気に Ruby を書いているという事で
 Ruby のクイズを作ってみました
 
 対象:Ruby 始めて3ヶ月〜
 動作環境:Ruby2.7.0


Slide 5

Slide 5 text

こういう暗号読解系はやらない 󰢃
 (敬虔な Rubyist なら2秒で解けるらしいけど?)
 ちなみに
 puts !????!:!?!

Slide 6

Slide 6 text

Q 変数代入

Slide 7

Slide 7 text

Q. 変数代入 何が出力されるでしょう? a = 1, 2 p a b, c = 1 p b p c d, *e = 1, 2, 3 p d p e

Slide 8

Slide 8 text

Q. 変数代入 a = 1, 2 p a => [1, 2] b, c = 1 p b => 1 p c => nil d, *e = 1, 2, 3 p d => 1 p e => [2, 3] ❖ 右辺がカンマ区切りで複数ある場合には配列に変 換される ❖ 左辺の要素が余った場合、 nil で初期化される ❖ アスタリスクを利用するとまとめて代入できる

Slide 9

Slide 9 text

Q 定数

Slide 10

Slide 10 text

Q. 定数 CONST = "constant" p CONST CONST = "overwrite!" p CONST CONST.reverse! p CONST 何が出力されるでしょう?

Slide 11

Slide 11 text

Q. 定数 CONST = "constant" p CONST => "constant" CONST = "overwrite!" p CONST => "overwrite!" CONST.reverse! p CONST => "!etirwrevo" ❖ アルファベット大文字で始まる識別子は定数 ❖ 定数と言いつつ再代入が可能 ❖ 定数と言いつつ破壊的変更が可能

Slide 12

Slide 12 text

Q. 定数(freeze) CONST = "constant".freeze p CONST CONST = "overwrite!".freeze p CONST CONST.reverse!.freeze p CONST 何が出力されるでしょう?

Slide 13

Slide 13 text

CONST = "constant".freeze p CONST => "constant" CONST = "overwrite!".freeze p CONST => "overwrite!" CONST.reverse!.freeze => can't modify frozen String (FrozenError) Q. 定数(freeze) ❖ freeze で凍結しても再代入は可能 ➢ あくまでもオブジェクトの参照を入れ替えてい て、元々のオブジェクトを変更するわけではない から ❖ 破壊的な変更は RuntimeError になる ❖ もし厳格に定数を定義したい場合は Class や Module ごと凍結する module MyConst CONST = 'constant'.freeze freeze end p MyConst::CONST => "constant" MyConst::CONST = "overwrite!" => can't modify frozen Module

Slide 14

Slide 14 text

Q. 定数(array) list = ["apple", "banana", "orange"].freeze list.map {|x| x << " juice" } p list list << "berry juice" p list 何が出力されるでしょう?

Slide 15

Slide 15 text

Q. 定数(array) list = ["apple", "banana", "orange"].freeze list.map {|x| x << " juice" } p list => ["apple juice", "banana juice", "orange juice"] list << "berry juice" => can't modify frozen Array (FrozenError) ❖ array を freeze で凍結しても中身の要素に破壊的 変更を加えることは可能 ❖ array 自体への変更は RuntimeError になる ❖ もし array や hash の中身の要素まで凍結したい 場合は map 関数で一つ一つ freeze する list = ['apple', 'banana', 'orange'].map(&:freeze).freeze list.map {|x| x << ' juice' } => can't modify frozen String: "apple" (FrozenError)

Slide 16

Slide 16 text

Q nil

Slide 17

Slide 17 text

Q. nil p nil.to_i p nil.to_s p nil.empty? 何が出力されるでしょう?

Slide 18

Slide 18 text

p nil.to_i => 0 p nil.to_s => "" p nil.empty? => undefined method `empty?' for nil:NilClass (NoMethodError) Q. nil ❖ nil もオブジェクトなのでレシーバとして機能する ➢ Ruby では全てがオブジェクト ➢ nil は NilClass のインスタンスオブジェクト ❖ メソッドが見つからない時は NoMethodError ❖ Object class と Kernel Module に実装されている メソッドなら呼び出し可能 p nil.class.ancestors => [NilClass, Object, Kernel, BasicObject]

Slide 19

Slide 19 text

❖ ちなみに Ruby のクラスはオープン ➢ 既存クラスを拡張できる ❖ 右の例ではデフォルトでは存在しない empty? メ ソッドを追加したり、既存の to_s メソッドの挙動を変 更したりしている ➢ でもモンキーパッチダメゼッタイ ... ❖ method_missing は呼び出されたメソッドが見つか らない時に実行されるメソッド ➢ つまりこれを使うと nil レシーバ呼び出しによ る RuntimeError を100%回避できる ➢ でもモンキーパッチダメゼッタイ ... class NilClass def empty?; true; end def to_s; raise "にるぽだよーん"; end def nil.method_missing(*_); nil; end end p nil.empty? => true p nil.hello => nil p nil.to_s => `to_s': にるぽだよーん (RuntimeError) Q. nil

Slide 20

Slide 20 text

Q safe navigation

Slide 21

Slide 21 text

Q. safe navigation def hello_or_nil ["hello", nil].sample end p hello_or_nil.upcase 手軽に nil safety にするには...?

Slide 22

Slide 22 text

def hello_or_nil ["hello", nil].sample end p hello_or_nil&.upcase Q. safe navigation ❖ safe navigation operator 「&」を使うと、nil レシー バに対する呼び出しで例外が起きなくなる ➢ Ruby2.3.0 〜 ➢ ぼっち演算子とも呼ばれる

Slide 23

Slide 23 text

Q. safe navigation require "active_support/all" def hello_or_nil ["hello", nil].sample end p hello_or_nil&.upcase p hello_or_nil.try(:upcase) ぼっち演算子と active support の try との違いはなんでしょう?

Slide 24

Slide 24 text

❖ ぼっち演算子 ➢ レシーバが nil なら nil を返す ➢ 右の例では 1 に対する呼び出しはエラーに なる ❖ try ➢ NoMethodError を握りつぶす ➢ レシーバが何かを意識しない ➢ 右の例では常にエラーが発生しない Q. safe navigation require "active_support/all" def hello_or_nil_or_1 ["hello", nil, 1].sample end p hello_or_nil_or_1&.upcase p hello_or_nil_or_1.try(:upcase)

Slide 25

Slide 25 text

❖ ぼっち演算子を使うと、引数はメソッドが呼び出され た時のみ評価される ➢ try の場合は常に評価される ❖ 処理効率もぼっち演算子の方が高速かつ、 rails に ロックインしないため、理由がなければぼっち演算 子を使うと良い Q. safe navigation def sugoku_omoi_fn sleep 1000 end # こうではなく res = sugoku_omoi_fn obj&.foo(res) # こう obj&.foo(sugoku_omoi_fn)

Slide 26

Slide 26 text

Q 配列のソート

Slide 27

Slide 27 text

arr1 = [ "d", "a", "e", "c", "b" ] p arr1.sort arr2 = [9, 7, 10, 11, 8] p arr2.sort arr3 = ["9", "7", "10", "11", "8"] p arr3.sort Q. 配列のソート 何が出力されるでしょう?

Slide 28

Slide 28 text

arr1 = [ "d", "a", "e", "c", "b" ] p arr1.sort => ["a", "b", "c", "d", "e"] arr2 = [9, 7, 10, 11, 8] p arr2.sort => [7, 8, 9, 10, 11] arr3 = ["9", "7", "10", "11", "8"] p arr3.sort => ["10", "11", "7", "8", "9"] Q. 配列のソート ❖ 内部的には <=> 演算子で各要素を比較している ❖ 数値の場合は見たまんまの並び替えが起こる ❖ 文字列の場合はバイト列比較なので見た目に反し た挙動になる ➢ バイト列に変換し、その数値を先頭から比 較していく p "10".bytes => [49, 48] p "9".bytes => [57] p "hoge" if "10" < "9" => "hoge" # 49 < 57 なので

Slide 29

Slide 29 text

Q booleanと真偽

Slide 30

Slide 30 text

p "blank" if "" p "zero" if 0 p "true" if true p "false" if false p "nil" if nil Q. booleanと真偽 何が出力されるでしょう?

Slide 31

Slide 31 text

❖ Ruby では false と nil が「偽」で、それ以外は全て 「真」 p "blank" if "" => "blank" p "zero" if 0 => "zero" p "true" if true => "true" p "false" if false => p "nil" if nil => Q. booleanと真偽

Slide 32

Slide 32 text

❖ ちなみに Boolean 型というのは存在しない ➢ TrueClass と FalseClass があるのみ ❖ true は TrueClassの、false は FalseClass の唯 一のインスタンスオブジェクト ➢ 擬似変数としてグローバルに定義され凍結さ れているかつ new が呼べないので、プログ ラム上で常に不変の object id を持つ ➢ (シングルトンにしてるのかなと思ったら new を undef してるっぽい) Q. booleanと真偽 p true.class.ancestors => [TrueClass, Object, Kernel, BasicObject] p false.class.ancestors => [FalseClass, Object, Kernel, BasicObject] b1, b2 = true, true p b1 == b2 => true p b1.equal?(b2) => true p TrueClass.new => undefined method `new'

Slide 33

Slide 33 text

Q スコープ

Slide 34

Slide 34 text

Q. スコープ if true var = "if_true" end p var (1..1).each do var2 = "do_each" end p var2 何が出力されるでしょう?

Slide 35

Slide 35 text

❖ Ruby はレキシカルスコープ ❖ if や for はスコープを作らない ❖ each はスコープを作る ➢ 正確にはブロックがスコープを作る ➢ ブロックはコードだけでなく束縛の集まりでも ある ❖ あれ、でもブロックの外側で count 的な変数を定義 してそれをイテレータブロックの中で増やしていくみ たいなコードって書けるよな ...? if true var = "if_true" end p var => "if_true" (1..1).each do var2 = "do_each" end p var2 => undefined local variable or method `var2' Q. スコープ

Slide 36

Slide 36 text

❖ ブロックはスコープを作ると同時にコンテキストという考え 方も持っている ❖ ブロックはクロージャであり、ブロック内の自由変数はブ ロックの外部環境(コンテキスト)に従う ❖ つまりメソッド実行時のローカル変数を参照できる count = 1 3.times do count += 1 end p count => 4 Q. スコープ def create_counter count = 1 return Proc.new do count += 1 p count end end counter = create_counter p counter.class => Proc counter.call => 2 counter.call => 3 ❖ ブロックが参照している外部環境は、ブロックが存在する 限り保存されている ❖ create_counter メソッドの実行時コンテキストにおける ローカル変数 count は、 メソッドが返した Proc 以外か らは参照できない ➢ 内部状態を完全に隠蔽できる

Slide 37

Slide 37 text

top_var = "top" MyClass = Class.new do p top_var define_method :my_method do p top_var end end MyClass.new.my_method => "top" => "top" ❖ ちなみに Ruby のスコープゲートは以下の 3つ ➢ class ➢ module ➢ def ❖ Ruby のインタプリタは上記のキーワードをもとにス コープを作るかどうかを判定している ❖ つまり右のような書き方でスコープゲートの利用を 回避して束縛を渡すことが可能 ❖ 他にも instance_eval とか... Q. スコープ

Slide 38

Slide 38 text

Q ファーストクラスオブジェクト

Slide 39

Slide 39 text

def hello(name) puts "Hello #{name}" end hello_fn = hello hello_fn("kitty") Q. ファーストクラスオブジェクト 何が出力されるでしょう?

Slide 40

Slide 40 text

❖ func への代入時の右辺評価時に hello を実行しよ うとするが、引数がないためエラーになる ❖ Ruby では関数定義を変数に代入することはできな い ➢ 関数そのものがファーストクラスオブジェクト ではないため ❖ 関数定義を一級関数化するには Object.method でメソッドオブジェクト化してあげる def hello(name) puts "Hello #{name}" end hello_fn = hello => # wrong number of arguments (ArgumentError) hello_fn("kitty") Q. ファーストクラスオブジェクト def hello(name) puts "Hello #{name}" end hello_fn = Object.method(:hello) hello_fn.call("kitty") => "Hello kitty"

Slide 41

Slide 41 text

def hello Proc.new { |name| puts "Hello #{name}" } end hello_fn = hello hello_fn.call("kitty") => "Hello kitty" hello = Proc.new do |name| puts "Hello #{name}" end hello.("kitty") => "Hello kitty" ❖ 実用的には Proc や lambda オブジェクトを使うこと が多い(と思う) ➢ Proc は手続きをまとめた”オブジェクト”なの で値として扱える ❖ call メソッドを呼ばないといけないのは、つけないと 変数として解釈されるから ❖ そのまま変数にぶち込めば無名関数として使える Q. ファーストクラスオブジェクト

Slide 42

Slide 42 text

Q 探索

Slide 43

Slide 43 text

HOGE = "TOP" class A HOGE = "A" end class B < A def hoge p HOGE end end B.new.hoge Q. 探索 何が出力されるでしょう?

Slide 44

Slide 44 text

❖ 定数の探索順序はレキシカルスコープ →スーパー クラスチェーン→トップレベル HOGE = "TOP" class A HOGE = "A" end class B < A def hoge p HOGE end end B.new.hoge => "A" Q. 探索

Slide 45

Slide 45 text

class A HOGE = "A" def hoge p HOGE end end class B < A HOGE = "B" end B.new.hoge Q. 探索 何が出力されるでしょう?

Slide 46

Slide 46 text

❖ メソッド探索は継承ツリーに沿って行われ、スー パークラスのメソッドを呼び出せる ❖ メソッド呼び出し時のレキシカルスコープが有効に なるので class A の定数が参照される ❖ この状態で class A の定数定義を削除すると、 HOGE が見つからずエラーになる ➢ 探索対象が A::HOGE だから class A HOGE = "A" def hoge p HOGE end end class B < A HOGE = "B" end B.new.hoge => "A" Q. 探索

Slide 47

Slide 47 text

puts = "1" puts puts Q. 探索 何が出力されるでしょう?

Slide 48

Slide 48 text

❖ 変数とメソッド定義は名前空間が異なるので同じ名 前をつけることができる ❖ まず変数を探索し、なければローカルメソッドを探索 する ❖ あれ、それだと「"1" "1"」 という解釈になるので は...? puts = "1" puts puts => 1 Q. 探索

Slide 49

Slide 49 text

puts = "1" puts # 変数参照 puts() # メソッド参照 puts(puts) # = puts puts = メソッド参照 ❖ 呼び出し時に () がついていればメソッド ❖ () が省略されているだけとみなし、メソッドだと解釈 される Q. 探索

Slide 50

Slide 50 text

Q 参照の値渡し

Slide 51

Slide 51 text

def add_fuga(arr) arr.push("fuga") end arr1 = ["hoge"] p add_fuga(arr1) p arr1 Q. 参照の値渡し 何が出力されるでしょう?

Slide 52

Slide 52 text

Q. 参照の値渡し def add_fuga(arr) arr.push("fuga") end arr1 = ["hoge"] p add_fuga(arr1) => ["hoge", "fuga"] p arr1 => ["hoge", "fuga"] ❖ add_fuga の引数には arr1 のポインタが渡されて いる ➢ 参照先が同じオブジェクトなので push の変 更が arr1 にも影響している

Slide 53

Slide 53 text

def add_fuga_with_new(arr) arr = Array.new arr.push("fuga") end arr2 = ["hoge"] p add_fuga_with_new(arr2) p arr2 Q. 参照の値渡し 何が出力されるでしょう?

Slide 54

Slide 54 text

Q. 参照の値渡し def add_fuga_with_new(arr) arr = Array.new arr.push("fuga") end arr2 = ["hoge"] p add_fuga_with_new(arr2) => ["fuga"] p arr2 => ["hoge"] ❖ add_fuga_with_new の中で arr に再代入している ので、arr2 とは別のオブジェクトが生成される ➢ 元の arr2 には psuh の変更が影響しない ❖ 参照渡しの場合は引数 arr の参照自体が切り替 わってしまうため、arr2 の参照も置き換わるはずだ が、Ruby は参照ではなく参照の値(ポインタ)を渡し ているだけなのでこのような挙動になる ❖ Java や Ruby は参照渡しじゃないよおじさん「 Java や Ruby は参照渡しじゃないよ」

Slide 55

Slide 55 text

❖ ちなみに String や Intger なども同様に全て参照の 値渡し ➢ 知らずに破壊的変更を加えると普通にバグ ります def add_fuga(str) str << "fuga" end str = "hoge" add_fuga(str) p str => "hogefuga" Q. 参照の値渡し

Slide 56

Slide 56 text

❖ 引数で渡した段階ではポインタを渡しているので同 じオブジェクトを見ている ❖ 再代入したタイミングで新しいオブジェクトが生成さ れる ➢ メモリ効率を鑑みて、再利用されるまで実体 をコピーしないようになっている ❖ スコープが異なるので元の str には影響しない def add_fuga(str) p str.object_id => 60 str = "fuga" p str.object_id => 80 end str = "hoge" p str.object_id => 60 add_fuga(str) p str => "hoge" Q. 参照の値渡し

Slide 57

Slide 57 text

これで君も Ruby マスターだ!!

Slide 58

Slide 58 text

解説 https://www.m3tech.blog/entry/2019/04/25/123843 ちなみに puts !????!:!?! => false

Slide 59

Slide 59 text

Fin