Vim Advent Calendar 2012 の226日目の記事です。昨日の記事は@supermomongaさんで、明日の記事は@manga_osyoさんです。
前回の記事によりますと、
明日216日目は私が勝手に師匠としている@ujihisaさんです。 ちなみに前回のujihisaさんによる記事をお覚えでしょうか。前回の前々回、つまり前々々回の記事が投稿された日はujihisaさんの誕生日です。
とのことでした。それはさておき、今回はVitalのProcessManagerはひとまずおいておいて、Vitalに新しく追加されたData.LazyListについて解説しましょう。
ClojureやRubyなど大抵のプログラミング言語で積極的に用いられているのが遅延リストです。遅延シーケンスや、ストリームとよばれることもあります。(Pythonなどの)ジェネレータも遅延リストのもつ機能の一部をもっています。
いろんな言語で遅延リストの例をみてみましょう。まずはRubyです。
[1, 2, 3].lazy # to get an Enumerator::Lazy object
(0..1/0.0).lazy.select {|i| i % 2 == 0 }.take(3).to_a #=> [0, 2, 4]
File.open('/tmp/a.txt') do |io|
xs = io.each_line
p xs.select {|x| x.size >= 5 }.take(3)
end
1/0.0
は無限大になります。(0..1/0.0).lazy
で0から1つずつ大きくなっていく無限超の遅延リストを生成し、そのうちselect {|i| i % 2 == 0 }
により偶数部分のみ取得するように予約し、最後にtake(3)
で先頭3つを取得しています。つづいてファイルの中身を取得する方は、/tmp/a.txt
というとても長いかもしれないファイルから、その全体を走査することなく、一行の文字数が5文字以上であるような行を3つだけ取得してそのファイルを閉じるということを行なっています。
(0..1/0.0).lazy.
select {|i| i % 2 == 0 }.
map {|i| -i }.
take_while {|i| i > -5 }.
to_a #=> [0, 2, 4]
ちょっと複雑な例を作ってみました。map
とtake_while
を用いています。
上記すべての例をClojureでも書いてみましょう。面白いことに、Clojureでは多くのリスト処理の操作が遅延されます。明示的に遅延化したければlazy-seq
を用いますが、そうでなくてもfor
などを使うだけで勝手に遅延化されています。
(lazy-seq [1 2 3])
(take 3 (iterate inc 0))
;=> [0 2 3]
(take-while #(> % -5)
(map #(- %)
(filter #(= (mod % 2) 0)
(iterate inc 0))))
; or..
(take-while #(> % -5)
(for [i (iterate inc 0)
:when (= (mod i 2) 0)]
(- i)))
;=> (0 -2 -4)
非常に直感的な記述ができました。遅延リストの概念は、操作の組み合わせを記述するのが容易で、かつ後にそのコードを読んだり編集したりするのを容易にします。
Vim scriptで遅延リストを行うためには、VitalのData.LazyListを用います。 VitalのData.LazyListはujihisaさんが週末を潰して一気に作り上げたライブラリです。
前述の例をData.LazyListを用いたVim scriptで記述すると以下のようになります。
L.from_list([1, 2, 3]) " to get an Enumerator::Lazy object
L.take(3, L.filter('v:val % 2 == 0', L.iterate(0, 'v:val + 1'))) " [0, 2, 4]
let xs = L.file_readlines('/tmp/a.txt')
echo L.take(3, L.filter('len(v:val) >= 5', xs))
Ruby側に比べて、より簡潔に記述することができました。
Vim scriptには値としての関数をその場に記述する方法がないため、文字列を用いています。使用の際にはスコープに気をつけてください。
Data.LazyListの関数一覧:
from_list(list)
file_readlines(fname)
iterate(init, f)
zip(xs, ys)
is_empty(xs)
filter(xs, f)
map(xs, f)
take(n, xs)
take_while(xs, f)
first(xs, default)
rest(xs, default)
drop(n, xs)
参照用リンク
- https://github.com/vim-jp/vital.vim/blob/master/doc/vital-data-lazylist.txt
- http://rbtnn.github.io/how-to-use-vital.vim/pages/Data.LazyList.html
:h Vital.Data.LazyList-datastructure
にも書きましたが、ここでも簡単に触れておきましょう。
s:from_list([3, 1, 4]) ==
\ [[], {'list': [3, 1, 4], 'run': function('<SNR>385__f_from_list')}]
遅延リストは内部的にはタプル (二要素のリスト) で実現しており、左が適用予定関数達、右がジェネレータです。ジェネレータは辞書で、必ず'run'という要素を含まなければなりません。
ジェネレータをたんなるfuncrefにせず辞書にしたのは、状態をもたせるためです。Vim scriptのfunctionはclosureではないためです。
なんとこのタイミングでvital.vim読書会というのが企画されているようです。開催は明日、日本時間だと14日(日曜)の午前11時開始です。