SlideShare a Scribd company logo
Phantom Type in Scala	
 
ファントムタイプ社の社長にみんなでPhatom Typeを教える会



        2012.10.06 前田康行(@maeda_)
自己紹介	
 

•  前⽥田康⾏行行 (@maeda_ )


•  好きな⾔言語
  •  Scala
  •  Smalltalk


•  DyNagoya (Dynamic language + Nagoya)
  •  http://dynagoya.info
About Phatom Type	
 

A phantom type is a parametrised type whose
parameters do not all appear on the right-hand
side of its definition



- Haskell wiki より抜粋 -

http://www.haskell.org/haskellwiki/Phantom_type
幽霊型の例	
 
•  型パラメータTはcase classのフィールドに使われて
   いない
case class A[T](x : Int)

scala> A[String](3)
res0: A [String] = A(3)

scala> A [List[Char]](3)
res1: A [List[Char]] = A(3)

scala> res0 == res1
res2: Boolean = true

•  特にScalaの場合、Type Erasureにより実⾏行行時に型パ
   ラメータは消える
Phantom Typeは幽霊なのか?	
 

•  Phantom Type(幽霊型)は
 •  実⾏行行時ではなく、コンパイル時にのみ暗躍する。
 •  条件を満たさないと、コンパイルエラーにして実⾏行行
    できなくする呪いをかける
 •  コンパイルが通ると成仏していなくなる


•  これは幽霊ですね
Phatom Typeの実用例	
 1
            Type Safeな構文木	
 
    •  Expr[T]は評価結果がT型となることを表す
trait Expr[T]
case object True extends Expr[Boolean]
case object False extends Expr [Boolean]
case class Number(x: Int) extendsExpr [Int]
case class Plus(x1: Expr [Int], x2:Expr [Int]) extends Expr [Int]

scala> Plus(Plus(Number(3),Number(4)),Number(5))
res4: Plus = Plus(Plus(Number(3),Number(4)),Number(5))

scala> Plus(Plus(Number(3), Number(4)), True) // PlusにBooleanは渡せない
<console>:14: error: type mismatch;
 found : True.type
 required: Expr[Int]
          Plus(Plus(Number(3), Number(4)), True)
Phatom Typeの実用例	
 2
             状態を表す(定義)	
 
sealed trait State
class Empty private () extends State
class Ready private () extends State

class Dinner[Oeuvre <: State, MainDish <: State, Dessert <: State] (){
   def cookSalad = new Dinner[Ready, MainDish, Dessert]
   def cookSoup = new Dinner[Ready, MainDish, Dessert]
   def cookSteak = new Dinner[Oeuvre, Ready, Dessert]
   def cookCake = new Dinner[Oeuvre, MainDish, Ready]

  def serve() = println("Now you can eat!")
}
object Dinner{
  def start = new Dinner[Empty, Empty, Empty]
}
Phatom Typeの実用例	
 2
          状態を表す(実行例)	
 
scala> Dinner.start
res0: Dinner[Empty, Empty, Empty] = Dinner@4b401150


scala> Dinner.start.cookSalad.cookCake
res1: Dinner[Ready, Empty, Ready] = Dinner@6d9e4f58


scala> Dinner.start.cookSalad.cookCake.serve()
Now you can eat!


scala> Dinner.start.cookSalad.cookSteak.cookSoup.serve()
Now you can eat!
ただのタグじゃなくて、
コンパイルエラーにしたい
そこで
 Implicit Parameter
(暗黙のパラメータ)
      ですよ
implicit parameterの例	
 
// implicit parameter付きの関数の宣⾔言
scala> def func(implicit ev: Int) = m
// implicit parameterが⾒見見つからないのでエラー
scala> func
<console>:9: error: could not find implicit value for parameter
m: Int

// スコープ内にimplicitで宣⾔言した値があると、⾃自動的に渡される
scala> implicit val v = 3
scala> func // → 3

 •  スコープ内のimplicitで宣⾔言された値の中で、
    型が合う値をコンパイル時に探索する
多相的なimplicit parameter	
 
scala> def implicitly[T](implicit ev: T) = m

scala> implicit val v = 3
scala> implicitly[Int]         // → 3
scala> implicitly[String]      // →  コンパイルエラー


 •  implicitlyメソッドはPredefに標準で定義されている

 •  型パラメータで指定された型のimplicitに取得できる
    値を探索
もう少しお行儀よく	
 
class Tag[A]()
def func[A](implicit ev: Tag[A]) = "OK
implicit val intAllowed = new Tag[Int]


scala> func[Int]       // → OK
scala> func[String]     // →コンパイルエラー

 •  普通に使う型をimplicitで宣⾔言するのはよくない

 •  implicitで使⽤用するための型を⽤用意して、その型
    パラメータで特定の型を指定する⽅方がいい
型パラメータを2つにする	
 
class Tag[A, B] ()
def func[A, B](implicit ev: Tag[A, B]) = "OK"
implicit val intAllowed = new Tag[Int, Int]
implicit val stringAllowed = new Tag[String, String]

scala> func[Int, Int]    // OK
scala> func[Int, String] // コンパイルエラー

 •  個別に使⽤用可能な型の組み合わせをimplicitで宣
    ⾔言する
任意の2つの型が同じである
   ことを表現したい	
 
•  型パラメータを持った値は扱えない

   implicit val x[A] = new Tag[A, A]


•  メソッドなら型パラメータを持てる
 ○ implicit def f[A] = new Tag[A, A]
2つの型が同じ場合
         呼び出し可能な関数	
 
class Tag[A, B] ()
implicit def equalAllowed[A] = new Tag[A, A]
def func[A, B](implicit ev: Tag[A, B]) = "OK


scala> func[Int, Int]    // → OK
scala> func[Int, String] // → コンパイルエラー
インスタンス生成の無駄を省く	
 
class Tag[A, B] ()
val singletonTag = new Tag[Any, Any]
implicit def equalAllowed[A]: Tag[A, A] =
           singletonTag.asInstanceOf[Tag[A, A]]

def func[A, B](implicit ev: Tag[A, B]) = "OK"

 •  implicitで呼び出される度にインスタンス⽣生成するのは無駄

 •  implicitで渡される値は使われないので、型さえあえば、中
    ⾝身はなんでもいい

 •  nullでもOK
2つの型パラメータをとる型の
   シンタックスシュガー	
 
scala> class Op[A, B] ()

scala> val x: Op[Int, String] = new Op
x: Op[Int,String] = Op@ab612f8

scala> val x: Int Op String = new Op
x: Op[Int,String] = Op@165e6c89

•  中置記法の⽅方が型の演算⼦子っぽく⾒見見える
•  クラス名に記号も使える。記号の⽅方がより演算⼦子っぽい
Scala 2.10-M7の実装	
 
sealed abstract class =:=[From, To]
                  extends (From => To) with Serializable
private[this] final val singleton_=:= =
        new =:=[Any,Any] { def apply(x: Any): Any = x }
object =:= {
    implicit def tpEquals[A]: A =:= A =
      singleton_=:=.asInstanceOf[A =:= A]
}

•  From => To を継承しているため、型を変換する関数とし
   て使える
def func[A](x1: A)(implicit w: A =:= B ) = {
  val x2 = w(x1)             // x2はB型として使える
  ...
}

•  Serializableを継承している利⽤用は不明
generalized type constraints	
 
scala> implicitly[Int =:= Int]
res7: =:=[Int,Int] = <function1>


scala> implicitly[Int =:= String]
<console>:9: error: Cannot prove that Int =:= String.
         implicitly[Int =:= String]
                 ^


    •  異なる型を渡すとコンパイルエラー
Prove?	
 

これは、あの証明ですか?	
 



      ← あの証明です
証明は身近な存在	
 

•  カリー・ハワード同型対応
 •  型 ⇆ 命題
 •  プログラム ⇆ 証明


•  プログラムに型が付く ⇆ 命題が証明された



•  「A =:= B」型について、それを満たすimplicitをコ
   ンパイラが⾒見見つけて、型が付けば、「AがBであるこ
   と」が証明されている
定理証明支援器 Coq	
 

•  型の表現⼒力力がすごい
   例:迷路の経路が最短経路である型
   http://d.hatena.ne.jp/yoshihiro503/20100119



•  そんな型を満たすようなプログラムを書くのは⼤大
   変
  •  Coqは⼈人にやさしい形で⽀支援してくれる
一方、Scalaは	
 

•  型レベルλ計算
   http://apocalisp.wordpress.com/
   2010/06/08/type-level-programming-in-
   scala/
•  型レベル命題論理
   http://www.chuusai.com/2011/06/09/scala-
   union-types-curry-howard/


•  ⼀一応、がんばれば、、、
まとめ	
 

•  Phantom Type(幽霊型)はコンパイル時に暗躍

•  使いこなすには型レベルでいろいろやりたくなる

•  Scalaでは、implicit parameterを利⽤用して、コ
   ンパイル時にいろいろできる

•  Coqすごい
もっと型の力を知りたい人は	
 
•  ProofCafe(毎⽉月第4⼟土曜)
  •  名古屋でプログラムの証明について勉強する勉強会で
     す。現在はソフトウェアの基礎というドキュメントを
     読んでいます。コーヒーを飲みながら楽しく証明しま
     しょう。
     http://proofcafe.org/wiki/

•  TAPL-nagoya (毎⽉月第3⼟土曜)
  •  ScalaやF#やSML#などの静的型付け⾔言語の基礎に
     なっている型理論についての勉強会です。英語ですが、
     説明が丁寧で例が豊富な TAPL をみんなで読みます。
  •  http://proofcafe.org/tapl/
その他
<%< はなくなった
            (2.10から)	
 
 •  「A => B」でOK

scala> implicitly[ Int <%< Long ]
<console>:12: error: not found: type >%>
         implicitly[ Int <%< Long ]
                      ^


scala> implicitly[ Int => Long ]
res5: Int => Long = <function1>
<%<は、なぜなくなったのか	
 


•  SI-2781

•  type inference constraints should be
   carried along during search for chained
   implicits

•  https://issues.scala-lang.org/browse/
   SI-2781
演習問題	
 1	
 
•  9ページの例をコンパイルエラーを出すようにする

scala> Dinner.start.cookSalad.cookCake.serve()
<console>:8: error: Cannot prove that Dinner.Empty =:=
Dinner.Ready.
         Dinner.start.cookSalad.cookCake.serve()
                                  ^
scala> Dinner.start.cookSalad.cookCake.cookSalad
<console>:8: error: Cannot prove that Dinner.Ready =:=
Dinner.Empty.
         Dinner.start.cookSalad.cookCake.cookSalad
                               ^

scala> Dinner.start.cookSalad.cookCake.cookSteak.serve()
Now you can eat!
演習問題 2	
 
•  AがBの⼦子クラスであるかをチェックする型を考えてみる

scala> trait A
scala> trait B with A

scala> implicitly[ B <:< A ]
res9: <:<[B,A] = <function1>

scala> implicitly[ A <:< B ]
<console>:14: error: Cannot prove that A <:< B.
         implicitly[ A <:< B ]
参考文献	
 
•  Type classes as objects and implicitsBruno
   C.d.S. Oliveira, Adriaan Moors,
   Martin Odersky
   OOPSLA ʻ‘10 Proceedings of the ACM
   international conference on Object oriented
   programming systems languages and
   applications, 2010

  この論⽂文の「6.6 Encoding generalized
  constraints」に>:>の話がある(少しだけど)

More Related Content

Phantom Type in Scala

  • 1. Phantom Type in Scala ファントムタイプ社の社長にみんなでPhatom Typeを教える会 2012.10.06 前田康行(@maeda_)
  • 2. 自己紹介 •  前⽥田康⾏行行 (@maeda_ ) •  好きな⾔言語 •  Scala •  Smalltalk •  DyNagoya (Dynamic language + Nagoya) •  http://dynagoya.info
  • 3. About Phatom Type A phantom type is a parametrised type whose parameters do not all appear on the right-hand side of its definition - Haskell wiki より抜粋 - http://www.haskell.org/haskellwiki/Phantom_type
  • 4. 幽霊型の例 •  型パラメータTはcase classのフィールドに使われて いない case class A[T](x : Int) scala> A[String](3) res0: A [String] = A(3) scala> A [List[Char]](3) res1: A [List[Char]] = A(3) scala> res0 == res1 res2: Boolean = true •  特にScalaの場合、Type Erasureにより実⾏行行時に型パ ラメータは消える
  • 5. Phantom Typeは幽霊なのか? •  Phantom Type(幽霊型)は •  実⾏行行時ではなく、コンパイル時にのみ暗躍する。 •  条件を満たさないと、コンパイルエラーにして実⾏行行 できなくする呪いをかける •  コンパイルが通ると成仏していなくなる •  これは幽霊ですね
  • 6. Phatom Typeの実用例 1 Type Safeな構文木 •  Expr[T]は評価結果がT型となることを表す trait Expr[T] case object True extends Expr[Boolean] case object False extends Expr [Boolean] case class Number(x: Int) extendsExpr [Int] case class Plus(x1: Expr [Int], x2:Expr [Int]) extends Expr [Int] scala> Plus(Plus(Number(3),Number(4)),Number(5)) res4: Plus = Plus(Plus(Number(3),Number(4)),Number(5)) scala> Plus(Plus(Number(3), Number(4)), True) // PlusにBooleanは渡せない <console>:14: error: type mismatch; found : True.type required: Expr[Int] Plus(Plus(Number(3), Number(4)), True)
  • 7. Phatom Typeの実用例 2 状態を表す(定義) sealed trait State class Empty private () extends State class Ready private () extends State class Dinner[Oeuvre <: State, MainDish <: State, Dessert <: State] (){ def cookSalad = new Dinner[Ready, MainDish, Dessert] def cookSoup = new Dinner[Ready, MainDish, Dessert] def cookSteak = new Dinner[Oeuvre, Ready, Dessert] def cookCake = new Dinner[Oeuvre, MainDish, Ready] def serve() = println("Now you can eat!") } object Dinner{ def start = new Dinner[Empty, Empty, Empty] }
  • 8. Phatom Typeの実用例 2 状態を表す(実行例) scala> Dinner.start res0: Dinner[Empty, Empty, Empty] = Dinner@4b401150 scala> Dinner.start.cookSalad.cookCake res1: Dinner[Ready, Empty, Ready] = Dinner@6d9e4f58 scala> Dinner.start.cookSalad.cookCake.serve() Now you can eat! scala> Dinner.start.cookSalad.cookSteak.cookSoup.serve() Now you can eat!
  • 11. implicit parameterの例 // implicit parameter付きの関数の宣⾔言 scala> def func(implicit ev: Int) = m // implicit parameterが⾒見見つからないのでエラー scala> func <console>:9: error: could not find implicit value for parameter m: Int // スコープ内にimplicitで宣⾔言した値があると、⾃自動的に渡される scala> implicit val v = 3 scala> func // → 3 •  スコープ内のimplicitで宣⾔言された値の中で、 型が合う値をコンパイル時に探索する
  • 12. 多相的なimplicit parameter scala> def implicitly[T](implicit ev: T) = m scala> implicit val v = 3 scala> implicitly[Int]         // → 3 scala> implicitly[String]      // →  コンパイルエラー •  implicitlyメソッドはPredefに標準で定義されている •  型パラメータで指定された型のimplicitに取得できる 値を探索
  • 13. もう少しお行儀よく class Tag[A]() def func[A](implicit ev: Tag[A]) = "OK implicit val intAllowed = new Tag[Int] scala> func[Int] // → OK scala> func[String] // →コンパイルエラー •  普通に使う型をimplicitで宣⾔言するのはよくない •  implicitで使⽤用するための型を⽤用意して、その型 パラメータで特定の型を指定する⽅方がいい
  • 14. 型パラメータを2つにする class Tag[A, B] () def func[A, B](implicit ev: Tag[A, B]) = "OK" implicit val intAllowed = new Tag[Int, Int] implicit val stringAllowed = new Tag[String, String] scala> func[Int, Int] // OK scala> func[Int, String] // コンパイルエラー •  個別に使⽤用可能な型の組み合わせをimplicitで宣 ⾔言する
  • 15. 任意の2つの型が同じである ことを表現したい •  型パラメータを持った値は扱えない implicit val x[A] = new Tag[A, A] •  メソッドなら型パラメータを持てる ○ implicit def f[A] = new Tag[A, A]
  • 16. 2つの型が同じ場合 呼び出し可能な関数 class Tag[A, B] () implicit def equalAllowed[A] = new Tag[A, A] def func[A, B](implicit ev: Tag[A, B]) = "OK scala> func[Int, Int] // → OK scala> func[Int, String] // → コンパイルエラー
  • 17. インスタンス生成の無駄を省く class Tag[A, B] () val singletonTag = new Tag[Any, Any] implicit def equalAllowed[A]: Tag[A, A] = singletonTag.asInstanceOf[Tag[A, A]] def func[A, B](implicit ev: Tag[A, B]) = "OK" •  implicitで呼び出される度にインスタンス⽣生成するのは無駄 •  implicitで渡される値は使われないので、型さえあえば、中 ⾝身はなんでもいい •  nullでもOK
  • 18. 2つの型パラメータをとる型の シンタックスシュガー scala> class Op[A, B] () scala> val x: Op[Int, String] = new Op x: Op[Int,String] = Op@ab612f8 scala> val x: Int Op String = new Op x: Op[Int,String] = Op@165e6c89 •  中置記法の⽅方が型の演算⼦子っぽく⾒見見える •  クラス名に記号も使える。記号の⽅方がより演算⼦子っぽい
  • 19. Scala 2.10-M7の実装 sealed abstract class =:=[From, To]                   extends (From => To) with Serializable private[this] final val singleton_=:= = new =:=[Any,Any] { def apply(x: Any): Any = x } object =:= { implicit def tpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A] } •  From => To を継承しているため、型を変換する関数とし て使える def func[A](x1: A)(implicit w: A =:= B ) = { val x2 = w(x1) // x2はB型として使える ... } •  Serializableを継承している利⽤用は不明
  • 20. generalized type constraints scala> implicitly[Int =:= Int] res7: =:=[Int,Int] = <function1> scala> implicitly[Int =:= String] <console>:9: error: Cannot prove that Int =:= String. implicitly[Int =:= String] ^ •  異なる型を渡すとコンパイルエラー
  • 22. 証明は身近な存在 •  カリー・ハワード同型対応 •  型 ⇆ 命題 •  プログラム ⇆ 証明 •  プログラムに型が付く ⇆ 命題が証明された •  「A =:= B」型について、それを満たすimplicitをコ ンパイラが⾒見見つけて、型が付けば、「AがBであるこ と」が証明されている
  • 23. 定理証明支援器 Coq •  型の表現⼒力力がすごい 例:迷路の経路が最短経路である型 http://d.hatena.ne.jp/yoshihiro503/20100119 •  そんな型を満たすようなプログラムを書くのは⼤大 変 •  Coqは⼈人にやさしい形で⽀支援してくれる
  • 24. 一方、Scalaは •  型レベルλ計算 http://apocalisp.wordpress.com/ 2010/06/08/type-level-programming-in- scala/ •  型レベル命題論理 http://www.chuusai.com/2011/06/09/scala- union-types-curry-howard/ •  ⼀一応、がんばれば、、、
  • 25. まとめ •  Phantom Type(幽霊型)はコンパイル時に暗躍 •  使いこなすには型レベルでいろいろやりたくなる •  Scalaでは、implicit parameterを利⽤用して、コ ンパイル時にいろいろできる •  Coqすごい
  • 26. もっと型の力を知りたい人は •  ProofCafe(毎⽉月第4⼟土曜) •  名古屋でプログラムの証明について勉強する勉強会で す。現在はソフトウェアの基礎というドキュメントを 読んでいます。コーヒーを飲みながら楽しく証明しま しょう。 http://proofcafe.org/wiki/ •  TAPL-nagoya (毎⽉月第3⼟土曜) •  ScalaやF#やSML#などの静的型付け⾔言語の基礎に なっている型理論についての勉強会です。英語ですが、 説明が丁寧で例が豊富な TAPL をみんなで読みます。 •  http://proofcafe.org/tapl/
  • 28. <%< はなくなった (2.10から) •  「A => B」でOK scala> implicitly[ Int <%< Long ] <console>:12: error: not found: type >%> implicitly[ Int <%< Long ] ^ scala> implicitly[ Int => Long ] res5: Int => Long = <function1>
  • 29. <%<は、なぜなくなったのか •  SI-2781 •  type inference constraints should be carried along during search for chained implicits •  https://issues.scala-lang.org/browse/ SI-2781
  • 30. 演習問題 1 •  9ページの例をコンパイルエラーを出すようにする scala> Dinner.start.cookSalad.cookCake.serve() <console>:8: error: Cannot prove that Dinner.Empty =:= Dinner.Ready. Dinner.start.cookSalad.cookCake.serve() ^ scala> Dinner.start.cookSalad.cookCake.cookSalad <console>:8: error: Cannot prove that Dinner.Ready =:= Dinner.Empty. Dinner.start.cookSalad.cookCake.cookSalad ^ scala> Dinner.start.cookSalad.cookCake.cookSteak.serve() Now you can eat!
  • 31. 演習問題 2 •  AがBの⼦子クラスであるかをチェックする型を考えてみる scala> trait A scala> trait B with A scala> implicitly[ B <:< A ] res9: <:<[B,A] = <function1> scala> implicitly[ A <:< B ] <console>:14: error: Cannot prove that A <:< B. implicitly[ A <:< B ]
  • 32. 参考文献 •  Type classes as objects and implicitsBruno C.d.S. Oliveira, Adriaan Moors, Martin Odersky OOPSLA ʻ‘10 Proceedings of the ACM international conference on Object oriented programming systems languages and applications, 2010 この論⽂文の「6.6 Encoding generalized constraints」に>:>の話がある(少しだけど)