けいじゅ@日本ラショナルソフトウェアです.

禁断のcoerceの話題が出てきてしまいましたね(^^;;;

# 新しいcoerceの話は, ruby-2.0に関する話が解禁になるまで取り敢えずペンディ
# ングということになっていました.

In [ruby-ext :00382 ] the message: "[ruby-ext:00382] New coerce scheme
", on Aug/13 09:37(JST) Takashi Nishimoto writes:

>新しい coerce の方法が思い付きました。

新しいというか... この案は, Smalltalkでのcoerceメカニズムそのものですね.
coerceに関しては過去にかなり深い議論がありますので, それらを全て読んでか
らにされたほうがよいかと思います.

>ちょっと dev 向けかもしれませんが、拡張ライブラリレベルで実装できるた
>め、ここで言います。こちらではちゃんと動いています。

うーん. こういうことはdevだと思います. extは拡張ライブラリについての疑問
点などを議論すべきところですから.

ということで, devにふります. 今後はdevでお話ししましょう.

>現状では演算が定義されているクラス (数、行列など。総称として 
>Calculable と呼んでみます) のメソッド定義は以下のようになっていますが、
>
>class Calc1
>  def +(other)
>    case other
>    when Calc1
>      ...
>    when Numeric
>      ...
>    else
>      x, y = other.coerce(self)
>      x + y
>    end
>  end
>  
>  def coerce(other)
>    case other
>    when Numeric
>      [ ... ]
>    else
>      raise TypeError, "#{type} can't be coerced into #{other.type}"
>    end
>  end
>end
>
>これではクラス定義を大幅に変更しなくては、数ではない Calculable を追加
>することができません。行列、ベクトル、区間、自動微分なんかがそれに当た
>りますね。もっとあると思いますが。上のスクリプトでは「区間型」との演算
>ではエラーを起こします。
>
>かと言って、クラス定義の中に他の Calculable の名前を含めるのは、カプセ
>ル化の観点から良くないと思います。だって、新たな Calculable の定義を追
>加すると、他の Calculable の定義も書き換えなければなりませんから。
>
>「クラス定義はそのクラス同士に対する演算で閉じている」
>
>べきなのです。そうすれば新たな Calculable の追加で他のクラスに影響を及
>ぼすことはほぼ皆無に近いです。

えーと. これは, 今のcoerceの仕組みを完全に誤解されていると思います. まさ
に, 西本さんがおっしゃっていることを実現するためにcoerceの仕組みはできて
います.

coerceはどのように考えてできているかと言いますと... 話はじめると長くなり
ますので, まだ出版されていない例のRuby本から引用しますと以下のようになっ
ています.

-- ここから.

Rubyのcoerceアーキテクチャの基本的な考え方は, 以下のようなものです.

1. ある数値関連クラスNがクラスMとの演算に関し対応している場合には, その
   クラスで演算を行う.
2. クラスNが演算に対応していないクラスMに関しては, 相手のクラスMに型変換
   (coerce)をお願いし, 再演算を行う. 

2の相手のクラスに対してcoerceをお願いするのが, coerceアーキテクチャのミ
ソになります.

では, より具体的にどのように処理を行っていくのか調べていきましょう[リス
ト: coerce].

--
class 数値関連クラス
  def +(other)
    when other
    case 自分が知っているクラス1
      # それなりの計算を行う
    case 自分が知っているクラス2
      # それなりの計算を行う
	      :
    else
      # 自分の知らないクラスの場合
      # (A-1) 相手のクラスで型変換を行い   
      coerced_me, coerced_other = other.coerce(self)
      # (A-2) その結果で演算する
      coerced_me + coerced_other
    end
  end

  def coerce(other)
    when other
    case 自分の知っているクラス1
      # (B-1) self, other共にそれなりの型変換を行う
      return coerced_other, coerced_me
    case 自分の知っているクラス2
      return coerced_other, coerced_me
		:
    else
      # (B-2) 上位クラスに投げる
      super
    end
  end
end
-- [リスト:coerce] coerceの仕組み

[リスト:coerce]の概要をまとめると以下のようになります.

* 自分の知っているクラスに関しては, その場でそれなりの処理を行います.
* 自分の知らないクラスに関しては, 相手に対して適当な型変換をお願いします
  (A-1). 
* その結果を再び演算するようになっています(A-2).
* また, 型変換のメソッドcoerceでは, 自分の知っているクラスでは, それなり
  に型変換を行います(B-1). 
* 知らないクラスに対しては, superを実行するようにします(B-2)(-- これは, 
  別の数値クラスが, Numericのサブクラス(か子孫)である場合です. もし,
  Numericのサブクラスでない時には, 変換できない旨の適当な例外を発生させ
  る必要があります. --).

-- ここまで

西本さんの例で何がまずかったかというと

class Calc1
  def +(other)
    case other
    when Calc1
      ...
    when Numeric # <--- ここ
      ...
    else
      x, y = other.coerce(self)
      x + y
    end
  end
  
  def coerce(other)
    case other
    when Numeric # <--- ここ
      [ ... ]
    else
      raise TypeError, "#{type} can't be coerced into #{other.type}"
    end
  end
end

``# <--- ここ'' のところですね. ここでは, Calc1が本当にしっているクラス
だけを書くようにします. そうすれば, Calc1が知らないクラスがでてきたばあ
い, 相手のクラスにcoerceをお願いして再演算しますので, 相手がこっちのクラ
スを知っていれば無事演算可能になります.

つまり, coerceは後から実装するクラスが既存クラスへの対応を考えよ. って考
えになっています.

(中略)

>とします。こちらで定義している(する予定の)優先度は
>
>200 行列、ベクトル
>100 自動微分
> 20 区間
> 18 複素数
> 15 分数
> 10 ほかの Numeric

最初にも話しましたが, これはSmalltalkのcoerceでRubyでも最初この形にする
という話がありましたが, 結局現在の形に落ち着きました.

ここいらの議論は過去のcoerceの議論を参照してほしいと思いますが. 結論を言
いますと, 数のクラスは線形順序付けられるようにはに並んでいないだろうとい
うのが結論です. たとえば, 固定少数点数と浮動小数点数の優先度はどうします
か? どっちが優先ということもありませんよね?

あと, もう1つ問題があって, 西本さんのですと

  Numeric * Matrix

が実現できるようにすると, 本来エラーになるはずの

  Numeric + Matrix

が計算できてしまうでしょう? 

今の, coerceはクラス間の変換を(アルゴリズムを用いて)グラフ状に変換関係を
定めているようなかんじです. ですので, 西本さんの案より自由度があります. 
自由度があるのはいいが実装がめんどくさいというのも事実で, もうちょっと静
的に変換関係が定義できないか? というあたりでcoerceの議論は止っています.

__
..............................石塚 圭樹@日本ラショナルソフトウェア...
----------------------------------->> e-mail: keiju / rational.com <<---