ある型クラスのインスタンスを定義するとき、何らかの規則を満たす必要があるときがあります。
例えばMonadだとモナド則。
bind,returnを定義するときに、以下のものが満たされないといけません。
bind (return a) f = f a
bind m return = m
bind (bind m f) g = bind m (fun x => bind (f x) g)
しかし、Scalazの型クラスなどはモナド則を満たしているかどうかを検査してくれるわけではありません。
そこで、コンパイル時に規則を満たしているかどうかを検査するように型クラスを定義してみます。
Monoidを例に考えます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trait Monoid[M] { | |
type Zero <: M | |
type Append[A <: M, B <: M] <: M | |
def zero: Zero | |
def append[A <: M, B <: M](a1: A, a2: B): Append[A, B] | |
} |
ScalazのMonoidの定義と違うところは、ある型のサブクラスも許容しているところです。
Monoidは以下のものを満たす必要があります。
append zero a = a
append a zero = a
append a (append b c) = append (append a b) c
これをScalaで記述すると
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
trait Monoid[M] extends MonoidLow[M] { | |
type Zero <: M | |
type Append[A <: M, B <: M] <: M | |
def zero: Zero | |
def append[A <: M, B <: M](a1: A, a2: B): Append[A, B] | |
} | |
trait MonoidLow[M] { self: Monoid[M] => | |
def leftIdentity[A <: M](a: Append[Zero, A]): A | |
def rightIdentity[A <: M](a: Append[A, Zero]): A | |
def associativity[A <: M, B <: M, C <: M](a: Append[A, Append[B, C]]): Append[Append[A, B], C] | |
} |
となります。
型が命題でプログラムが証明になります。
つまり、leftIdentity,rightIdentity,associativityを定義出来れば証明できたことになります。
カリー=ハワード同型対応!
自然数を定義します。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Nat { | |
type +[N <: Nat] <: Nat | |
def +[N <: Nat](n: N): +[N] | |
} | |
sealed trait _0 extends Nat { | |
type +[N <: Nat] = N | |
def +[N <: Nat](n: N) = n | |
} | |
case object _0 extends _0 | |
case class S[N <: Nat](n: N) extends Nat { | |
type +[M <: Nat] = S[N# + [M]] | |
def +[M <: Nat](m: M) = S(n + m) | |
} |
自然数をMonoidのインスタンスにします。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
object NatInstance extends Monoid[Nat] { | |
type Zero = _0 | |
type Append[A <: Nat, B <: Nat] = A# + [B] | |
def zero = _0 | |
def append[A <: Nat, B <: Nat](a: A, b: B) = a + b | |
def leftIdentity[A <: Nat](a: Append[Zero, A]) = a | |
implicit def rightIdentity[A <: Nat](a: Append[A, Zero]) = a | |
implicit def associativity[A <: Nat, B <: Nat, C <: Nat](a: Append[A, Append[B, C]]) = a | |
} |
コンパイル通ったー!
通らない例を作ってみましょう。
正の整数を定義して、Monoidのインスタンスを考えてみます。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
sealed trait Pos { | |
type +[P <: Pos] <: Pos | |
def +[P <: Pos](p: P): +[P] | |
} | |
sealed trait _1 extends Pos { | |
type +[P <: Pos] = S[P] | |
def +[P <: Pos](p: P) = S(p) | |
} | |
case object _1 extends _1 | |
case class S[P <: Pos](p: P) extends Pos { | |
type +[Q <: Pos] = S[P# + [Q]] | |
def +[Q <: Pos](q: Q) = S(p + q) | |
} | |
object PosInstance extends Monoid[Pos] { | |
type Zero = _1 | |
type Append[A <: Pos, B <: Pos] = A# + [B] | |
def zero = _1 | |
def append[A <: Pos, B <: Pos](a: A, b: B) = a + b | |
def leftIdentity[A <: Pos](a: Append[Zero, A]) = a | |
implicit def rightIdentity[A <: Pos](a: Append[A, Zero]) = a | |
implicit def associativity[A <: Pos, B <: Pos, C <: Pos](a: Append[A, Append[B, C]]) = a | |
} |
この定義では当然コンパイルは通りませんね。
コンパイルが通るように定義することも多分できないと思われます。(asInstanceOfは使っちゃダメですよ。)
これらのコードを書いてる中で疑問が現れました。
NatInstanceのrightIdentityとassociativityです。
このコード、コンパイルは通らないと思っていたのですが、implicitを付けると通りました。
・・・・・なぜ?
※追記
@halcat0x15a こういうこと? gist.github.com/2908064
— Kenji Yoshidaさん (@xuwei_k) 6月 11, 2012
なんだか型推論に失敗してNothingを入れられちゃってるみたい?
下限境界は制限しようがないし、やはりScalaで証明は難しいということですね・・・・・
結論:Coqとか使いましょう。
0 件のコメント:
コメントを投稿