著作一覧 |
hfuさんのgeotoolsの実装で興味深い結果が。
読み間違いかも知れないけど、Javaのプリミティブ型クラスを返して、プリミティブ型クラスを受け取るオブジェクトに対する操作が主(かつ大量)になるため、JRubyが2倍遅いというのは、内部的なプリミティブ型クラスからRubyの型への変換に取られているらしい、ということ。
たしかに、Method#invokeを利用するときには、仮にプリミティブであっても、一度プリミティブ型クラスのオブジェクトに変換しなければならないから、Ruby側で特に操作をしないのであれば、プリミティブ型クラスのオブジェクトのままJavaからRuby、RubyからJavaへ動かしたほうが効率的だ。そうでなければ、JavaからRubyへの過程で変換して、さらにRubyからJavaへの過程でも変換することになる。(このとき、メソッドシグネチャとのマッチングに余分なコストもかかりそうに思う)
逆に、プリミティブ型クラスのオブジェクトをRuby側で操作しまくるのであれば、Rubyの型に変換されているほうがよほど効率的だし、ユーザビリティも高そうに思う。
でも、思うに、Javaでインターフェイスを決めるとき、わざわざ戻り値をプリミティブではなくプリミティブ型クラスのオブジェクトを選択した時って、その返送値は直接消費されるのではなく、一度コレクションへ格納するというような場合を想定することのほうが多いのではないだろうか。(というか、個人的には相当インターフェイスを決めたけど、プリミティブではなくプリミティブ型クラスを返送するメソッドとか、引数に撮るメソッドとか切った覚えがない。僕の感覚がどこまで一般的かはわからないけど、仮に一般的なインターフェイス決定にしたがっていると仮定すれば、わざわざプリミティブ型クラスのオブジェクトを返すというのは、そのままスルーすることを想定した場合に限定されるのではないだろうか。たとえばコレクションへ格納して後から取り出してまた送り込む場合とか)
というわけで、もしプリミティブ型クラスのオブジェクトのRubyの型への自動変換が、強制的なインターフェイスならば、JRubyの人は再考したほうが良いかも。と、今度、takaiさんに会ったら伝えよう(ここに書いても召還できないだろうしなぁ)。
そうすれば、hfuさんの例では、JRubyでもCRubyと同等の速度になる可能性があるように思う。(この場合、Rjb側から受けたプリミティブ型クラスのオブジェクトを一度Rubyの型に変換しているために、遅い方向で同等の速度になったわけだから)
OCamlの教科書読んでいるのだが、手元に処理系がないので、しょうがないのでGHCで例題を解いてるのだが、ストールしてしまった。
やりたいことは2つの関数の作成。
元の例題の実行例を書くと
let test1 = add_to_each 1 [] = [] let test2 = add_to_each 1 [[2]] = [1; 2]] let test3 = add_to_each 1 [[2]; [2; 3]] = [1; 2]; [1; 2; 3]]これはこう書いた。
ate n [] = [] ate n (f : t) = ((n : f) : ate n t)
期待通りに動く。
次の例題。
let test 5 = prefix [] = [] let test 6 = prefix [1] = [[1]] let test 7 = prefix [1; 2] = [[1]; [1; 2]] let test 8 = prefix [1; 2; 3; 4] = [[1]; [1; 2]; [1; 2; 3]; [1; 2; 3; 4]]
なんか、やっかいそうだけど、上で定義したate関数を使うってことはわかるので、とりあえず、最初に次のように書いてみた。
pref [] = [] pref (f : t) = (f : ate f t)ロードすると
*Main> :load test.hs Compiling Main ( test.hs, interpreted ) test.hs:60:24: Occurs check: cannot construct the infinite type: a = [a] Expected type: a Inferred type: [a] In the first argument of `ate', namely `f' In the second argument of `(:)', namely `ate f t' Failed, modules loaded: none.
アトムがほしいのにリストを寄越しているというエラーだと思うのだけど、なぜそうなるんだ?
というわけで、エラーメッセージを消す方向で、
pref [] = [] pref (f : t) = (f : ate (head f) t)
でも、これが正しく動くわけがない。
で、いったいなぜコンパイルが通らないのか混乱したということでした。
で、ateの引数とprefの引数というヒントのおかげというよりは、落ち着いて、まずはpref関数の枠組みを作って……とやっているうちに、できた、ということで。なんてトップダウンと正直なところ思ったけど。
ジェズイットを見習え |
JRubyの人というわけでもないけど、JRubyの人っぽい人なので召喚されました! Groovyと比べられる存在のJRubyとしては、RubyとJavaの型の違いを隠蔽したかったのかもしれません。一方で、こういう雰囲気を読み取って最適化するという方向性はまだまだないっぽいです。バイトコードにしてJVMが最適化するにまかせるという戦略もありそうですが、それも限界がありますし。
おお、どうもわざわざお越しいただき(ry<br>もちろん、現在の動作を変える必要はない(し、互換性がなくなるから無理)けど、まあ、こんなこともあるよ、ということで。<br>includeしたクラスごとにメソッドの型変換戦略みたいなものを設定して最適化するとか、いろいろできそうだと思います。僕もhfuさんのとこに書いたけど、ちょっとやってみるつもり。
# HaskellとOCamlとでは型エラーの出方が違うのですね.<br><br>ateの引数が[[a]]なのに,prefの中で[a]を渡しているからエラーになっています.外側のリストが取られて,[a]とaのエラーになってるという.(でもなんでoccur checkになるんだろ : OCamlとHaskellの違いかな…)
うわーーーー(投稿したら消えてたorz)
どうもありがとうございます。(コメントアウトで残ってます。やり直してやっぱりだめだったら復活させようと考えたので)<br>#完全な解答を導くまでの途中の状態で動作を見たいのが本意なのに、書き方が解答そのもののように書いてるので、もう少し試そうかなと思って引っ込めたのでした。すみません。
うーん、いきなり解答だと思うものを与えるとちゃんとコンパイルされるな(で試すと解答になってたのだが)。<br>中途半端なところでコンパイルしようとすると、マッチしなくなってエラーになるということなのか?<br>途中経過(やり直し版)- OK<br>pref0 [] = []<br>pref0 (f : t) = ([f] : pref0 t)<br>最終結果 - OK<br>pref [] = []<br>pref (f : t) = ([f] : ate f (pref t))
>外側のリストが取られて<br>ああ、わかったつもり。リストの各要素は同じ型じゃなければならないという前提の上で、prefに与えるリストの型と、ateに与えるべきリストの型が異なるのが原因ですね。<br>だから、途中経過とか言って元の引数を与えようとするのがだめなのか。先に内部で呼び出す関数を試すのではなく、先に再帰のほうを書かないと……というのが、教科書のテンプレートの意味なのか。(OCamlもそうかはわからないけど、多分、同じ考え方をすると思う)