65
怠惰なRubyistへの道 Enumerator::Lazyの使いかた Chikanaga Tomoyuki (@nagachika) Yokohama.rb 1

怠惰なRubyistへの道

Embed Size (px)

DESCRIPTION

みなとRuby会議01での発表資料の配布版

Citation preview

Page 1: 怠惰なRubyistへの道

怠惰なRubyistへの道 Enumerator::Lazyの使いかた

Chikanaga Tomoyuki(@nagachika)Yokohama.rb

1

Page 2: 怠惰なRubyistへの道

Agenda

Enumerable

Enumerator

Enumerator::Lazy

2

Page 3: 怠惰なRubyistへの道

WARNING

今日の話はRuby 2.0 の

新機能について

3

Page 4: 怠惰なRubyistへの道

Ruby 2.0

2013年リリース予定[ruby-dev:44691] より

Aug. 24 2012: big-feature freeze

Oct. 24 2012: feature freeze

Feb. 24 2013: 2.0 release

Ruby の生誕 20 周年

4

Page 5: 怠惰なRubyistへの道

Ruby 2.0

未来の話

5

Page 6: 怠惰なRubyistへの道

2.0 is in development

※注※仕様は開発中のものであり

リリース版では変更される場合があります

6

Page 7: 怠惰なRubyistへの道

Install Ruby 2.0

未来を手中に

rvm, rbenv を使っていれば

rvm install ruby-head

rbenv install 2.0-devel

7

Page 8: 怠惰なRubyistへの道

Ruby 2.0 for Windows

8

Page 9: 怠惰なRubyistへの道

Self Introduction

twitter id: @nagachika

ruby trunk changes(http://d.hatena.ne.jp/nagachika)

CRuby committer

Yokohama.rb → 福岡遠征中

Sound.rb(ruby-coreaudio, ruby-puredata)

9

Page 10: 怠惰なRubyistへの道

Enumerator::Lazy

10

Page 11: 怠惰なRubyistへの道

Enumerator::Lazy

の前に

11

Page 12: 怠惰なRubyistへの道

Enumerable

12

Page 13: 怠惰なRubyistへの道

Enumerable

もちろん使ってますよね?

13

Page 14: 怠惰なRubyistへの道

Array, Hash, IO...

Array や Hash, IO は Enumerable を

include している

14

Page 15: 怠惰なRubyistへの道

Enumerable

【形容詞】 数えられる [日本語 WordNet(英和))

可算;可付番 [クロスランゲージ 37分野専門語辞書]

Capable of being enumerated; countable [Wikitionary]

15

Page 16: 怠惰なRubyistへの道

Enumerable

「each メソッドを呼ぶと、何かが順に(N回) yield で渡される」というルール(N >= 0)

16

Page 17: 怠惰なRubyistへの道

Enumerable

each メソッドが定義されているクラスに include して使う(Mix-in)

17

Page 18: 怠惰なRubyistへの道

Enumerable

each だけ用意しておけばmap, select, reject, grep, etc...といったメソッドが使えるようになる

(これらのメソッドが each を使って実装されている)

18

Page 19: 怠惰なRubyistへの道

An Example

class A include Enumerable def each yield 0 yield 1 yield 2 endendA.new.map{¦i¦ i * 2 } # => [ 0, 2, 4 ]

19

Page 20: 怠惰なRubyistへの道

ここまでのまとめ

Enumerable は each メソッドを使って多彩なメソッドを追加するなにが「数え上げられる」のかはeach メソッドがどのようにyield を呼び出すかで決まる

20

Page 21: 怠惰なRubyistへの道

Enumerator

使ってます?

21

Page 22: 怠惰なRubyistへの道

Enumerator

each に渡すブロックを保留した状態でオブジェクトとして持ち回す

22

Page 23: 怠惰なRubyistへの道

to_enum, enum_for

Enumerator の作りかた

• Enumerator.new(obj, method=:each, *args)

• obj.to_enum(method=:each, *args)

• obj.enum_for(method=:each, *args)

• Enumerator.new{¦y¦ ...} ↑これだけちょっと特殊

23

Page 24: 怠惰なRubyistへの道

Object#enum_for

each 以外の任意のメソッドの呼び出しをEnumerator にできる(実は Enumerable でなくてもいい)

e_byte = $stdin.enum_for(:each_byte) => バイトごとに yield

e_line = $stdin.enum_for(:each_line) => 行ごとに yield

24

Page 25: 怠惰なRubyistへの道

Enumerator.new{¦y¦}

e = Enumerator.new{¦y¦ y << 0 y << :foo y << 2}

ブロックが each の代わり。ブロックパラメータ y に << すると yield することになる。

25

Page 26: 怠惰なRubyistへの道

Enumerator.new{¦y¦}

class A def each yield 0 yield :bar yield 2 endendA.new.to_enum

と同じ

26

Page 27: 怠惰なRubyistへの道

Enumerator as External Iterator

外部イテレータとして使える

e = (0..2).to_enume.next # => 0 each が yield する値がe.next # => 1 順に next で取り出せるe.next # => 2e.next=> StopIteration: iteration reached an end

27

Page 28: 怠惰なRubyistへの道

Method Chain

map, select など一部のメソッドはブロックを省略すると Enumerator を返すので複数のメソッドを組み合わせて使う

ary.map.with_index { ¦v,i¦ ... }

28

Page 29: 怠惰なRubyistへの道

Method Chain

[:a, :b, :c, :d].map.with_index do ¦sym, i¦ i.even? ? sym : iend=> [:a, 1, :c, 3] Enumerator

独自のメソッド

29

Page 30: 怠惰なRubyistへの道

with_index

[ruby-dev:31953] Matz wrote...

“mapがenumeratorを返すと

obj.map.with_index{¦x,i¦....}

ができるのが嬉しいので導入したのでした。というわけで、

* eachにもmapにもwith_indexが付けられて嬉しい

というのが本当の理由です。”

30

Page 31: 怠惰なRubyistへの道

Secret Story of Enumerator.

Enumerator は with_index の為に

生まれた!というのは嘘ですが

主な用途は with_index のためのような気がします

31

Page 32: 怠惰なRubyistへの道

ここまでのまとめ

• Enumerator• Object#to_enum, enum_for• 外部イテレータ化

• Method Chain•

32

Page 33: 怠惰なRubyistへの道

Enumerator::Lazy

2.0 の新機能

33

Page 34: 怠惰なRubyistへの道

Enumerator::Lazy

Enumeratorのサブクラス

34

Page 35: 怠惰なRubyistへの道

Enumerable#lazy

Enumerator::Lazy の作りかた

Enumerable#lazy を呼ぶ

>> (0..5).lazy

=> #<Enumerator::Lazy: 0..5>

35

Page 36: 怠惰なRubyistへの道

Enumerator::Lazy

Lazyのmap,select,zipなど一部のメソッドがさらにLazyを返す

>> (0..5).lazy.map{¦i¦ i * 2}

=> #<Enumerator::Lazy: #<Enumerator::Lazy: 0..5>:map>

36

Page 37: 怠惰なRubyistへの道

Enumerator::Lazy> Enumerator::Lazy.instance_methods(false)

map collect flat_map

collect_concat select find_all

reject grep zip

take take_while drop

drop_wihle cycle

lazy force

[ruby 2.0.0dev (2012-05-30 trunk 35844)]

37

Page 38: 怠惰なRubyistへの道

force

Enumrator::Lazy#force

to_a の alias

遅延されているイテレータの評価を実行

38

Page 39: 怠惰なRubyistへの道

A Lazy Demo>> l = (0..5).lazy=> <Enumerator::Lazy: 0..5>>> l_map = l.map{|i| p i; i * 2 }=> <Enumerator::Lazy: #<Enumerator::Lazy: 0..5>:map># この時点ではまだブロックの内容は実行されていない!>> l_map.force012345=> [0, 2, 4, 6, 8, 10]# force を呼ぶと実行される

39

Page 40: 怠惰なRubyistへの道

Enumeratorとの違い

Method Chain の使いかたがより強力に

中間データの抑制

40

Page 41: 怠惰なRubyistへの道

Pitfall of Enumerator (1)

map, select のようにブロックの戻り値を利用するメソッドを複数繋げることができない(しても意味がなくなる)

(0..5).map.select{¦i¦ i % 2 == 0 }

=> [ 0, 2, 4 ]

↑ map の意味がなくなっている

41

Page 42: 怠惰なRubyistへの道

True Method Chain

map や select といったメソッドもいくつでも Method Chain できる。

そう、Lazy ならね。

# 1 から 1000 のうち任意の桁に7を含む数を返す(1..1000).lazy.map(&:to_s).select{¦s¦ /7/ =̃ s}.force

42

Page 43: 怠惰なRubyistへの道

Pitfall of Enumerator (2)

map, select のようにブロックの戻り値を利用するメソッドを外部イテレータ化しても意味がない

e = (0..5).selecte.next # => 0e.next # => 1e.next # => 2e.next # => 3

↑ select の意味がなくなっている

43

Page 44: 怠惰なRubyistへの道

True External Iterator

map などにブロックを渡せるので適用した結果を順に取り出せる

l = (0..5).lazy.select{¦i¦ i.even? }l.next # => 0l.next # => 2l.next # => 4l.next # => StopIteration: iteration reached an end

44

Page 45: 怠惰なRubyistへの道

Efficient Memory Usage

Lazy は yield 毎に Method Chain の最後まで処理される

45

Page 46: 怠惰なRubyistへの道

Enumerable version

large_array.map{...}.select{...}

46

Page 47: 怠惰なRubyistへの道

Enumerable version

large_array.map{...}.select{...}

map

47

Page 48: 怠惰なRubyistへの道

Enumerable version

large_array.map{...}.select{...}

map select

48

Page 49: 怠惰なRubyistへの道

Enumerable version

large_array.map{...}.select{...}

map selectGCされる

49

Page 50: 怠惰なRubyistへの道

Enumerable version

large_array.map{...}.select{...}

map selectGCされる

map 結果のため

large_array と同じくらいの中間データが必要

50

Page 51: 怠惰なRubyistへの道

Lazy version

large_array.lazy.map{...}.select{...}.force

51

Page 52: 怠惰なRubyistへの道

Lazy version

large_array.lazy.map{...}.select{...}.force

52

Page 53: 怠惰なRubyistへの道

Lazy version

large_array.lazy.map{...}.select{...}.force

GC’ed

53

Page 54: 怠惰なRubyistへの道

Lazy version

large_array.lazy.map{...}.select{...}.force

54

Page 55: 怠惰なRubyistへの道

Lazy version

large_array.lazy.map{...}.select{...}.force

GC’ed

55

Page 56: 怠惰なRubyistへの道

Lazy version

large_array.lazy.map{...}.select{...}.force

56

Page 57: 怠惰なRubyistへの道

Lazy version

large_array.lazy.map{...}.select{...}.force

中間データが不要

57

Page 58: 怠惰なRubyistへの道

Lazy Chain Demo>> (0..4).lazy.select{|i| p “select:#{i}”; i.event? }.map{|i| p “map:#{i}”; i * 2 }.force“select:0”“map:0”“select:1”“select:2”“map:2”“select:3”“select:4”“map:4”=> [0, 4, 8]

58

Page 59: 怠惰なRubyistへの道

Lazy with IO

例) $stdin から空行を読むまでの各行の文字数の配列を返す

$stdin.lazy.take_while{¦l¦ ! l.chomp.empty?}.map {¦l¦ l.chomp.size }

59

Page 60: 怠惰なRubyistへの道

Lazy with IO

Enumerable だと空行まで先に読んでしまう。

Lazy なら1行読むたびに take_while/map のブロックが実行される

→ 逐次処理できる

60

Page 61: 怠惰なRubyistへの道

Lazy List

無限に続く要素を扱える

list = (0..Float::INFINITY)

list.map{¦i¦ i * 2}.take(5)

=> 返ってこない

list.lazy.map{¦i¦ i * 2 }.take(5).force

=> [0, 2, 4, 6, 8]

61

Page 62: 怠惰なRubyistへの道

Pitfall of Lazy?

メモ化されない(force を2回呼ぶともう一度 each が呼ばれる)

IOなど副作用のある each だと結果が変化するけどいいのかなぁ(いいかも)

enum_for のように each のかわりになるメソッドを指定できない

62

Page 63: 怠惰なRubyistへの道

Lazy as a Better Enumerator

Lazy はEnumerator の

正統進化版

63

Page 64: 怠惰なRubyistへの道

Happy Lazy Programming!

64

Page 65: 怠惰なRubyistへの道

One More Thing!

https://github.com/nagachika/lazy_sample

65