Edited at

Rubyと型とダックタイピング

More than 1 year has passed since last update.


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?(IO)多い 😣

Search · "is_a IO" language:Ruby.png



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? を使おう。



参考文献