原です。 >けいじゅ@日本ラショナルソフトウェアです. なんかお盆明けてみると、ずいぶん情勢が変わっていて、、、 >int/intですが, やはり結果はRationalにするのがよいと強く思うようになりま >した. うっ。まじっすか。(^_^; >CommonLispもそうであったというのに力付けられました(^^;;; それに, >Smalltalkもそうだったしたし... そうなのかあ。CommonLisp と Smalltalk ってがんばってるんだなあ。 >1. int/int -> rational だとパフォーマンスが悪いという人がいるが, Rubyで > はそういうことを気にするよりは, 分りやすさを気にすべき. パフォーマン > スを気にする人は、明示的に整除するかto_fすればよい. rational のパフォーマンスで一番気になるのは、演算を行うたびに gcd の計算をするところですよね。gcd ってのはかなり複雑な計算なんで、 それが Integer#/ の様な基礎的な位置にある演算子で、特に意識しない まま始まってしまうってのがやや気持ち悪いわけです。 ちょっと話それるけど、rational.rb では計算するたびに約分してます よね。もしかすると、ある程度分子と分母が大きくなってからとか、表 示する時に約分する事にするとパフォーマンスが上がるってことありま せんかね。 >2. int/int -> floatを主張する人も, rationalならまだ納得するはず. という > か, 文句はあってもあきらめるに違いない. 「int/int -> float」派は、この際論外という事で。:-) 「int/int -> rational」派にも2つあると思うのですけど、一つは 1 / 1 を rational の 1 とするのと、もう一つは 1 / 1 を int の 1 とするのと。 流れからすると、石塚さんと正木さんは後者ですね。後者だとすると例 えば 1/3 + 2/3 も int の 1 になるわけですね。それはちょっと気持ち 悪い。 というのは、まず、演算結果のクラスがその結果の様子によってころこ ろ変わるのが気持ち悪い。クラスをハンドリングするツール系のクラス で無い限り、メソッドは基本的には一定のクラスを返してほしい。もっ とも、Ruby にはすでに Bignum と Fixnum と2つの Intger のサブクラ スがあって、相互変換しているけど、こっちはその変換が頻繁に起こる わけでもなく、API も完全に一致しているので、話は別であると思う。 また、そのような仕様にすると、各演算では計算の最終段階で結果がど のクラスに属すべきかチェックを入れなければいけなくなる。これがパ フォーマンスを落とす。それに例えば、ある数が実は整数であるという 事実が大きな定理だったり、未解決問題だったりするわけです。まあ、 有理数で考える分にはそんなたいそうな事もないのですが、そもそもそ の様な「判断」を演算がするってのは、やりすぎの気がします。 それから、数学での習慣として、定義域だけではなく、値域も指定して はじめて写像の定義となるわけで、メソッドを写像になぞらえるなら (このなぞらえが妥当かどうかという問題はあるが)、結果によってク ラスが変わるのは、値域がふらふらしているという感覚があって、気持 ちが悪い。 もちろん、可能な値域全てに関して明確な共通仕様があればいいという 考えはあり得るが、それならそれを一つのクラスとして、表現すべきで は。例えば Integer を無くして、Ratinal のみにすべき、って事になら ないでしょうか。 「int/int -> rational」派には、「数は一つ」という考えのが根本にあ ります。これは非常に健康的な考えで、なんとかそれを実現しようとが んばるのは意義のある事だとは思うのですが、実現にはかなりしんどい。 一方、今の数学的な発想からすると、数は単独であるわけではなく、ま ず集合があって更に要素間の演算等が定義され、「系」を形作って初め て意味が出て来る訳です。 ある演算の結果が 1 だったら整数、1/2 だったら、有理数という訳だけ れど、1/2 だからといって有理数体 Q の元だと決めつけていいのでしょ うか。整数と有理数の間には Z(p) = {分母が素数pと素な有理数} みた いのもあります。1/2 は Z(3) とも Z(5) とも思えるわけで、1/2 にとっ て Rational が特別に選ばれるべきクラスとは言えない。(言えないけ ど「借りに」言ってしまうことに意味が無いとは言えないけど。)むし ろ潔く、演算の結果のクラスはあらかじめ固定しておくべきではないで しょうか。 Z(p) なんてあまり馴染みないですね。例えば(i = Complex(0,1) とし て) (2 + 2*i) / (1 + i) はどうですかね。「int/int -> rational」 派的には、Fixnum の 2 でしょうけど、(-2 + 2*i) / (1 + i) は Complex(0, 2) になる。似たような数の演算をして、片や非常に小さい クラス Fixnum だが、片や非常に大きいクラス Complex になる。2 と Complex(0, 2) をこんなにひどく差別して:-)、Complex(0, 2) と Complex(0.0, 2.0) をひとし並みに扱って、「自然な」演算と言えるの でしょうか?自然さを追求するなら ComplexFixnum を作りたくならない か、、、でもそれをやっていくときりがない。 一方「int/int -> int」派は「数はどの系(クラス)に属するかによっ て、演算の意味は違う」と、最初からあきらめているわけです。自然で あることに対してあきらめのいいのが現代的であると考えている。:-) >3. matrix.det みたいなものは, 今のメカニズムではどうやっても綺麗に解決しそ > うにない. これは綺麗に解決しないでいいのではないかなあ。 デフォルトの det は効率が悪くても割り算を利用しない方法を提供して、 さらに要素の状況によっては効率のいい方法をユーザー選べるようにし たらいいと思います。結局、効率の問題で不細工になるのは、よくある 事でしかたがない。本気で効率を考えるユーザーは、自分でアルゴリズ ムを選びたいって要求もあるだろうし。 >4. 今回のcomplex#**なんかも根にはこれがある. a ** b の話は / とは大分違いますね。** は T op T -> T に拘る必要 はないですよね。 環の枠組みで決まる場合、つまり b が非負整数、a が 1、a が -1 で b が分母が奇数の有理数、、、などの場合は自明な値を返すべきだけど、 それ以外、例えば 2**(-1) の場合は、例外にする(1 / 2 と解釈しない) かあるいは 0 にする、というのはありだと思います。これを「不自然」 という考え方もありますが、不自然なのは 2**(-1) の「値」ではなく、 整数のカテゴリで 2**(-1) を「させる事」の方なんだ、という考え方を します。 (負のfloat) ** float は、require "complex" しても NaN のまま、あ るいは例外でいいと思います。それはやはり「本来は複素数」という考 えをあまりしたくないからです。「本来」って何?と疑問に思います。 Complex(負のfloat) ** float がそれなりの値を返したとしても、それ は本来の値を返したというより、むしろ Complex の仕様に過ぎないと考え た方がいいのではないでしょうか。もちろんその仕様はなるべく「自然」 なものを選ぶべきだけど、そもそも「本来」や「自然」の定義が疑わし いわけだから、そんなに拘ることもないと思う訳です。 Math.sqrt も同様です。今の所、これの戻り値は Float 固定でいいと思 います。だって、C のライブラリに依存しているわけでしょう。もっと も Float.sqrt などを入れて、Math.sqrt が適宜いい値を返してくれて も面白いと思いますが。 ところでこれも別の話だけど、平方根を越えない最大の整数を返す、 Integer.sqrt ってのもあると便利かなあ。Math.sqrt(2) を 2 乗したと き 2 より大きいのか小さいのか、その情報が需要な局面てのもあるから。 いろんな事言ってますが(^^;、結局私の意見としては、「int/int -> rational でがんばってみるのもいいかもしれないが、int/int -> int という現状の仕様のままでも、それはいささかも Ruby の価値を落とし てはいない(数学的にも)。Rational は早く C で書き直して組み込み にして頂戴、よろしくお願いします。」というものです。 #どさくさ紛れの要望ですが、Rational(1, 2).inspect は #"Rational(1, 2)" じゃなくて "1/2" にしません?