2011年7月25日月曜日

Scala で Functional Dependency

Functional Dependencies in Scala
の自分なりの解釈をメモ。

Functional Dependencyとは?


Haskellで標準的に使われているパターンらしい。

Functional dependencies are used to constrain the parameters of type classes. They let you state that in a multi-parameter type class, one of the parameters can be determined from the others, so that the parameter determined by the others can, for example, be the return type but none of the argument types of some of the methods.

Quoted from: Functional Dependencies

Functional Dependencyは、型クラスのパラメータに制約をかけるために利用します。
例えば、複数の型パラメータを持つ型クラスについて、あるパラメータによって別のパラメータが決まる、という制約を表現することができます。
これを応用してメソッドを定義すると、メソッドの引数に無い型を戻り型にする、ということができます。

Java屋さん的に言えば、型クラスを、型安全にDependency Injectionする仕組みです。

Scalaでのコード例


Functoinal Dependencyは長いので、ここではfundepと略して説明します。

// 型クラスのパラメータとなる型
trait Matrix
trait Vector

// fundepの説明における"型クラスのパラメータに制約をかける"部分に対応します.
// MultiDepという型クラスのパタメータA, B, Cに制約をかけます.
trait MultDep[A, B, C]
// trait MultDep[-A, -B, +C]    // for Scala <= 2.9.0-1 these variance
//                              // annotations are required

// fundepの説明における
//  "複数の型パラメータを持つ型クラスについて、あるパラメータによって別のパラメータが決まる、とう制約を表現"
// しているのがここです.
//
// パラメータA, Bによって, 別のパラメータCが決まる, という制約を表現していると考えてください.
//
// 逆に言えば, ここで記述されていないパラメータの組み合わせは許さないのでコンパイルエラーにしたい, ということになります.

// A, BがMatrixなら, CはMatrixである
implicit object mmm extends MultDep[Matrix, Matrix, Matrix]
// AがMatrix, BがVectorなら、CはVectorである
implicit object mvv extends MultDep[Matrix, Vector, Vector]
// 以下、同様に。
implicit object mim extends MultDep[Matrix, Int, Matrix]
implicit object imm extends MultDep[Int, Matrix, Matrix]

// 利用例

implicitly[MultDep[Matrix, Matrix, Matrix]]
// ABCが制約を満たしていれば何らかのオブジェクトが返ってくる。
// 満たしていなければコンパイルエラー

// fundepの説明 "メソッドの引数に無い型を戻り型にする" に対応する例が以下です.
// メソッドの引数A, B以外の型Cを戻り型に指定しています.
// A, B, Cの組み合わせが上記のimplicit objectで表現されていないとコンパイルエラーになるわけです.
def mult[A, B, C](a : A, b : B)(implicit instance : MultDep[A, B, C]) : C = /** instanceのメソッドを呼び出すコード */

// これはコンパイル成功する
val r1 : Matrix = mult(new Matrix {}, new Matrix{})
val r2 : Vector = mult(new Matrix {}, new Vector{})
val r3 : Matrix = mult(new Matrix {}, 2)
val r4 : Matrix = mult(2, new Matrix {})

// これはコンパイル失敗する
// implicit object mvm extends MultDep[Matrix, Vector, Matrix]
// のようなimplicit objectが定義されていないので,
// 「MultiDep[Matrix, Vector, Matrix]のようなimplicit objectが見つかりません」というコンパイルエラーが出力される.
val r5 : Matrix = mult(new Matrix {}, new Vector{})

fundepの適用範囲

Functional Dependecyを利用すると、関数に対して、複数のパラメータを持つ型クラスを型安全にDIできるわけですが、実際にどういった実装に応用していくか?
関数の型パラメータによって処理が異なったり、型パラメータの組み合わせに制約がある場合に応用するとよさそうです。

関数の型パラメータの制約を、関数内の条件分岐として実装して、実行時に制約違反を発見するか、もしくはコンパイル時にコンパイルエラーとして出力するか?
コンパイル時にコンパイルエラーとして出力するほうが良いですよね。
Functional dependecyが使えます。

関数の型パラメータの組み合わせによる処理の違いを、関数内に実装するか、関数外の型クラスのinsntanceどちらに実装すると見通しや拡張性が良いか?
型クラスのinstanceですよね。
ここでもFunctional dependecyが使えるということです。

まとめ

  • Functional DependecyはHaskellで標準的に使われるパターン
  • 型クラスの型パラメータに制約をかけることができる
  • 関数の型パラメータによって処理が異なるとき、異なる部分を型クラスのインスタンスのメソッドとして抜き出すことで、コードの見通しを良くできる

参考


Thanks to:

0 件のコメント:

コメントを投稿