ようへいの日々精進XP

よかろうもん

2018 年 07 月 01 日 (日)

ジョギング

日課

  • おやすみ

博多デート

  • お中元やら炊飯器, トースター, バリカン等電化製品を買い揃えたりした

今日のるびぃ ~ だいぶん古いけど, 「Ruby 2.0 最速入門」という記事を写経する (1) ~

だいぶん古い WEB+DB Press Vol.73 の「一歩先ゆく Ruby」というコーナーで「Ruby 2.0 最速入門」という記事が試験にあたって参考になりそうだったので写経する. 尚, irb に動作確認環境は以下の通り.

$ ruby --version
ruby 2.1.10p492 (2016-04-01 revision 54464) [x86_64-linux]
$ irb --version
irb 0.9.6(09/06/30)

Enumerator::Lazy

Enumerator::Lazy とは, 遅延リストを手軽に導入し, 操作する為の仕組み.

irb(main):001:0> require 'date'
=> true
irb(main):002:0> def each_fridays
irb(main):003:1>   friday = Date.new(2018, 6, 29)
irb(main):004:1>   loop do 
irb(main):005:2*     yield friday
irb(main):006:2>     friday += 7
irb(main):007:2>   end
irb(main):008:1> end
=> :each_fridays
irb(main):009:0> fridays = enum_for(:each_fridays)
=> #<Enumerator: main:each_fridays>

上記のように enum_for を利用することで, each_fridays から Enumerator オブジェクトを生成することが出来る. 以下のように Enumerable#first を使うことで, 最初の 5 つの要素だけを取り出すことが出来る.

irb(main):013:0> puts fridays.first(5)
2018-06-29
2018-07-06
2018-07-13
2018-07-20
2018-07-27
=> nil

Enumerable#mapEnumerable#select を使って絞り込み等を行う場合, Object#each_friday ではいつまで経っても処理が終わらないことになる. これは mapselect 等のメソッドが, 配列全ての要素に対して処理を行う前提となっている為である.

irb(main):014:0> fridays.select {|d| d.day == 13 }.first(5)




... ループ ...

この状態を解決する為, Enumerator::Lazy を利用する.

irb(main):016:0> puts fridays.lazy.select {|d| d.day == 13 }.first(5)
2018-07-13
2019-09-13
2019-12-13
2020-03-13
2020-11-13
=> nil

Enumerator::Lazy を利用することで, その各要素を実際に計算する前に mapselect 等の配列に対する操作をメソッドチェインで書き連ねていくことが出来る. 尚, 操作した結果は Enumerator::Lazy となる為, 要素を取り出す為には force メソッドを呼び出す必要がある.

irb(main):020:0> puts fridays.lazy.select {|d| d.day == 13 }.take(5).force
2018-07-13
2019-09-13
2019-12-13
2020-03-13
2020-11-13
=> nil

もしくは, 先述のように firstinject 等の, 要素を利用せざる得ないメソッドを呼び出すことで自動的に各要素が計算される.

irb(main):021:0> puts fridays.lazy.select {|d| d.day == 13 }.first(5)
2018-07-13
2019-09-13
2019-12-13
2020-03-13
2020-11-13
=> nil

巨大なログファイルの中から一部の文字列を検索したい場合にも lazy を利用することで, 巨大な中間ファイルを用意することなく検索結果を取り出すことが出来る.

file = open('/path/to/large.log').each_line.lazy
file.select {|line|
  line ~= /FooBar/
}.each {|line|
  print line
}

Refinements

  • ある特定の状況下でのみ, 特定のクラスメソッド定義の変更を適用した状態出来る
  • モンキーパッチによるメソッドの再定義は, 影響がグローバルに及んでしまう可能性がある

以下のような単位変換のメソッドがあった場合.

class Numeric
  def inch
    self * 2.54
  end
end

以下のように利用可能.

irb(main):022:0> class Numeric
irb(main):023:1>   def inch
irb(main):024:2>     self * 2.54
irb(main):025:2>   end
irb(main):026:1> end
=> :inch
irb(main):027:0> 1.inch
=> 2.54
irb(main):028:0> 2.inch
=> 5.08

但し, この inch メソッドが他の同名メソッドに影響を与える可能性があるので, 局所的にこの inch メソッドを利用出来るようにする.

module InchAvailable
  refine Numeric do
    def inch
      self * 2.54
    end
    alias inches inch
  end
end

irb で実行してみる.

irb(main):001:0> module InchAvailable
irb(main):002:1>   refine Numeric do
irb(main):003:2*     def inch
irb(main):004:3>       self * 2.54
irb(main):005:3>     end
irb(main):006:2>     alias inches inch
irb(main):007:2>   end
irb(main):008:1> end
=> #<refinement:Numeric@InchAvailable>
# using メソッドで InchAvailable を有効化
irb(main):009:0> using InchAvailable; 1.inch
=> 2.54
# using メソッドで InchAvailable を有効化
irb(main):010:0> using InchAvailable; 2.inch
=> 5.08
# using していない場合には undefined method になる
irb(main):011:0> 1.inch
NoMethodError: undefined method `inch' for 1:Fixnum
... 略 ...

フムフム.