RubyとDaruのイテレータについて

最近RubyのDaruを使っていて、もやもやと考えていることがある。

しかし頭がまとまらない。理由の一つはRubyのコードしか書いたことがないので知らないプログラミング概念がたくさんあるからかもしれない。そういうのを勉強すれば、もっとクリアな理解が得られるかもしれないという気がする。

習熟できなくても、概念に触れることはできるはずで、無理だと思わずに、C++やらrustやら難しいものを適当にかじってみた方がいいのかもしれない。しかし、今考えていることをアウトプットしておく。

Daruの使い方を調べていて、混乱したのが、イテレータであった。map や each である。私はmapが大好きだ。しかし、どうも最近mapがいかがわしく感じるときがある。

DaruにはVectorというクラスがある。データフレームは、ArrayとHashとNumPyの機能が全部必要だから、新しいクラスが必要なのだ。mapすると、Arrayが返される。これが直感に反する。Arrayではなく、Vectorが返ってきた方がよいのではないか。

それに、Daruを全面的に信頼できなくて、結局to_aを使ってしまうのである。こうなると、ArrayやHashを使うことになり、Daruの機能を使わないことになる。

そして、一度mapに疑問を持つと、いかがわしく見えてくるものがある。ブロックである。

私は上のコードが好きだ。わかりやすい。一方で、自分でイテレータや、yield を書くほど、プログラミングできるわけではない。ブロックを受け取るようなメソッドを自分で作ることない。

話がとぶが、Rubyを使い始めた頃、メソッドがオブジェクトではないのがすっきりしなかった。動的にメソッドを定義したり、削除したりすることはできても、メソッド自体はオブジェクトではない。

みたいな書き方ができない。Rubyでは、メソッド、Proc、lambda、lambdaのあたらしい書き方、ブロックなど、関数っぽいもがたくさんあって、ゴチャゴチャしているのである。

最初はDaruのイテレータが混沌としているのは、Daruの設計が悪いからだと思った。

けれども、ひょっとするとこれはArrayやHashを使わずに独自のクラスを設計すると常に発生する問題かもしれないと感じたのである。なんでもやってくれるArrayと、mapと、オブジェクトではないブロックの組み合わせは、ひょっとすると、本当はいびつなのかもしれない。どんな作業でもArrayを使うからあまり気にならないだけで。

だとすると、Arrayによく似たオブジェクトとイテレータを設計するのは難しい。もしApache ArrowのようなバックエンドのライブラリがRuby用の新しいデータフレームに採用されても、Rubyらしいイテレータを作成しようとするとまた困ってしまう。

map はブロックではなく、オブジェクトを引数に取る方がきれいかもしれない。そして、ブロック、メソッドは1種類のオブジェクトの方が美しいかもしれない。mapはArray以外のオブジェクトに対しても適応されることがあるので、Array以外のオブジェクトを返却する方が自然かもしれない、などと素人考えで思ったりする。

ruby 2.6 が出たので触ってみて、python と比較してみた
という記事を読んでみて、lambdaをフル活用したRubyが新鮮だったこともあり、そんなことを考えた。

こういう素朴な記事は、よく知っている人にとっては、恥ずかしくて書けないんだろうと思う。わからないことだらけだ。

想像だけど、きっとブロックをオブジェクトにするには、なにか実装に大きな問題があるのだと思う。そうでなければ、何でもオブジェクトにしたがるRubyで、ここまでグチャグチャしているはずがない。ブロックは、周囲のスコープを眺めることができる。つまり、ブロックは定義された位置に依存する存在なので、オブジェクトにするのには何か支障があるのかもしれない。

日本語でもそういうことはよくある。「県立図書館」がどの建物を意味するのかは、その単語が使われた都道府県によって全然違うだろう。ヒトは情報を処理する時に、そういう位置依存・文脈依存的な処理をフル活用している。自分だけではなくて、周囲環境が考えてくれてると言ってもいいかもしれない。

データをクレンジングを行う作業のうちいくらかの部分は、そのような文脈依存を剥ぎ取るための作業だと感じるときがある。

「RubyとDaruのイテレータについて」への4件の返信

  1. おもしろく読みました。一応こういう書き方は可能です。

    a = proc {puts “Hello!”}
    b = Class.new
    c = b.new
    c.define_singleton_method(:hello, &a)

    c.hello #=>Hello!

    変数 a に proc を使っているのは、Method だと必ずクラスが必要なので、無名関数である proc を使っているということです。それから、ブロックも Method も Ruby ではオブジェクトで、例えば上の &a というのは proc をブロックに変換しています。(逆にブロックを proc オブジェクトに変換することもできます。)そして、proc を Method の「中身」にしているのです。

    それから、Daru は知らないのですけれど、map が Array を返してしまうのがダメなら、Vector クラスに map と機能は同じで Vector を返すメソッドを新たに定義することはたぶんできると思います。でも、コレクション・クラスを作ったら、イテレータは用意してあって欲しいですけれどね。それがないというなら、やはりそれは Daru の欠陥ではないでしょうか。

    的外れなことを言っていたらごめんなさい。

  2. たびたびすみません。超テキトーなんですけど、例えばこんな感じでモンキーパッチしてみるとか。

    module Daru
    class Vector
    def map_v(&blk)
    map(&blk).daru_vector
    end
    end
    end

    a = Daru::Vector[1, 2, 3, 4].map_v {|i| i * 3}

  3. obeliskさんこんにちは

    ご無沙汰しています。

    > map が Array を返してしまうのがダメなら、Vector クラスに map と機能は同じで Vector を返すメソッドを新たに定義することはたぶんできると思います。

    ご存知かとおもいますが、DaruはRubyにおけるPandasのようなものです。
    実はDaru::Vectorは、recodeというメソッドを用意していて、これがVectorを返してくれます。このrecodeというメソッド名、個人的にはあまり馴染みがありませんでした。求めているのがrecodeだと気がつくまで時間がかかってしまい、怒ってQiitaにメモを書いたりしています。

    Daruがわかりにくいのはだいたいrecodeのせい
    https://qiita.com/kojix2/items/fd0b271cf171b063a1d4

    Enumerableモジュールをincludeして作られたクラスは、mapすると配列を返すようです。一方でNumo::NArrayのように、mapしても配列じゃなくて自分自身を返す場合もあります。

    PyCallの作者である@mrknさんに、「Rubyでコレクションクラスを自作した場合、mapは配列を返すので、自分自身を返すメソッドをrecodeという名前で定義するのは一般的な方法なのですか?」と聞いたところ、あまり一般的ではないだろう、という回答をもらいました。やはりわかりやすくないのです。

    ほかにも、Daruにはmapとcollectの挙動が微妙に違ったりとか、イテレーションまわりはかなり厄介なんですよね。何らかの対策が必要じゃないかなあと個人的には思います。

    Rubyのデータサイエンス界隈では、クリアコードの@kouさんの大活躍によりApche Arrow を推進していく流れになっているようですが、GObject introspectionを使って作られたプロダクツは、必然的にCっぽい抽象度の低いインターフェイスになってしまうので、これだけだとちと心配です。

    Ruby/Gtkバインディングをみていても、なんとなくブロックとの融合は鬼門感がありますね。

    Rubyはゆるふわなさわり心地に定評があるので、使いやすくてかわいいデータフレームがあるといいなと思うのですが。

    >モンキーパッチ

    上記のように recodeメソッドがあるので、大丈夫なのですが、ちゃんとDaruのソースコードを読んでみたほうがいいかもしれませんね。がんばります。

  4. ああ、recode というメソッドがあるのでしたか。それは失礼しました。
    素人なりに Ruby で遊んでおられるところに共感します。Ruby でデーターサイエンスとかおもしろいことをやっておられるなと思っていつも読んでいます。僕はいろんな言語(といっても知れていますが)を齧るだけは齧ったのですが、齧るほど Ruby がよくできていることに気づいて余計に Ruby 好きになるということになりました(笑)。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です