TokyuRuby会議12 (2018-07-29) 発表のスライドです
#tqrk12
@tkawa
- 川村 徹
- プログラマ
- ソニックガーデン
- REST厨
-
Sendagaya.rb
- 毎週月曜 19:30-
- 株式会社トクバイさんオフィス(渋谷)
- ゆるーいRubyコミュニティ
問題: users
の「型」は何でしょう?
def some_process(users)
users.each do |user|
puts user.email
# いろんな処理
end
end
- Array
- ActiveRecord::Relation
- 他にも……
配列の代わりに Enumerator
users = Enumerator.new do |y|
open('huge.csv') do |f|
f.each do |line|
y << User.new(line)
end
end
end
# こう書いてもできるけどファイルが close できないな 😔
# users = open('huge.csv').lazy.map{|line| User.new(line) }
ファイルを最初にすべてStringに読み込まなくてもOK
ファイルをダウンロードする場合にも応用できる
似た例: Rackアプリのレスポンス
app = Proc.new do |env|
[200, {'content-type' => 'text/plain'}, ["Hello world\n"]]
# [ステータス, ヘッダのhash, レスポンスボディ]
end
レスポンスボディの仕様は
each
でブロックパラメータに文字列が受け取れるオブジェクト
例えばIOやFileインスタンスをそのまま渡すことも可能。
問題: users
の「型」は何でしょう?
def some_process(users)
users.each do |user|
puts user.email
# いろんな処理
end
end
正解: each
でブロックパラメータに User
が受け取れるオブジェクト
(User
もクラスに関係なく email
他を実装していればよい)
このようにクラスやモジュールではなくメソッドによって型が決まるのが ダックタイピング
あなたが配列と思ったものは、実は配列ではないかもしれない
「型」の扱いが問題になる例
def prepare(source)
io = if source.is_a?(IO)
source
elsif source.is_a?(String)
open(source)
else
raise 'Invalid source'
end
…
source
がファイルならそのまま使う、文字列ならファイル名とみなしてopen
FileはIOだからこれでOK、と思いきや……
Tempfile.new.is_a?(IO) # => false
TempfileはFileでもIOでもない!!
def prepare(source)
io = case source
when IO, Tempfile # ←追加した
source
when String
open(source)
else
raise 'Invalid source'
end
…
さらに、StringIOもIOではない。
Rack::Test::UploadedFile も ActionDispatch::Http::UploadedFile も。。
全部足していくの。。😵
これは罠ではあるけれど、Rubyの欠陥ではない。
TempfileもStringIOもRubyの標準添付ライブラリ。
つまり、Rubyは最初から is_a?
ではうまくいかないようにできている。
何を期待しているのか?
IO のように振る舞うこと?
もっと直接的に言うと、each
のような特定のメソッドに応答すること。
もし io.read
したいのなら respond_to?
を使おう
def prepare(source)
io = if source.respond_to?(:read)
source
elsif source.is_a?(String)
open(source)
else
raise 'Invalid source'
end
…
is_a?
を使うのは特定のクラスだけ
Integer/Float/Numeric, String, Symbol, Hash, Date, Time, Module/Class
使う必要がないもの
Array → each
IO → read
/write
Proc → call
to_i
, to_f
, to_s
, to_a
, to_h
を使えば済む場合も多い。
もし「型チェックツール」が is_a?
を使うと同じ問題が起こる。respond_to?
を使おう。