310 likes | 429 Views
Phantom Type in Scala. ファントムタイプ社の社長にみんなで Phatom Type を 教える会 2012.10.06 前田康行 (@ maeda _). 自己紹介. 前田康行 (@ maeda _ ) 好きな言語 Scala Smalltalk DyNagoya ( Dy namic language + Nagoya ) http:// dynagoya.info. About Phatom Type.
E N D
Phantom Type in Scala ファントムタイプ社の社長にみんなでPhatomTypeを教える会 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のフィールドに使われていない • 特にScalaの場合、Type Erasureにより実行時に型パラメータは消える 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
Phantom Typeは幽霊なのか? • Phantom Type(幽霊型)は • 実行時ではなく、コンパイル時にのみ暗躍する。 • 条件を満たさないと、コンパイルエラーにして実行できなくする呪いをかける • コンパイルが通ると成仏していなくなる • これは幽霊ですね
Phatom Typeの実用例1Type 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] (){ defcookSalad = new Dinner[Ready, MainDish, Dessert] defcookSoup = new Dinner[Ready, MainDish, Dessert] defcookSteak = new Dinner[Oeuvre, Ready, Dessert] defcookCake = new Dinner[Oeuvre, MainDish, Ready] defserve() = 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! メインディッシュがない! 前菜2つも要らない!
ただのタグじゃなくて、コンパイルエラーにしたいただのタグじゃなくて、コンパイルエラーにしたい
そこでImplicit Parameter(暗黙のパラメータ)ですよ
implicit parameterの例 // implicit parameter付きの関数の宣言 scala> deffunc(implicitev: Int) = m // implicit parameterが見つからないのでエラー scala> func <console>:9: error: could not find implicit value for parameter m: Int // スコープ内にimplicitで宣言した値があると、自動的に渡される scala> implicitval v = 3 scala> func // → 3 • スコープ内のimplicitで宣言された値の中で、型が合う値をコンパイル時に探索する
多相的なimplicit parameter scala> def implicitly[T](implicitev: T) = m scala> implicitval v = 3 scala> implicitly[Int] // → 3 scala> implicitly[String]// → コンパイルエラー • implicitlyメソッドはPredefに標準で定義されている • 型パラメータで指定された型のimplicitに取得できる値を探索
もう少しお行儀よく class Tag[A]() deffunc[A](implicit ev: Tag[A]) = "OK” implicit valintAllowed = new Tag[Int] scala> func[Int] // → OK scala> func[String] // →コンパイルエラー • 普通に使う型をimplicitで宣言するのはよくない • implicitで使用するための型を用意して、その型パラメータで特定の型を指定する方がいい
型パラメータを2つにする class Tag[A, B] () deffunc[A, B](implicit ev: Tag[A, B]) = "OK" implicit valintAllowed = new Tag[Int, Int] implicit valstringAllowed = new Tag[String, String] scala> func[Int, Int] // OK scala> func[Int, String] // コンパイルエラー • 個別に使用可能な型の組み合わせをimplicitで宣言する
任意の2つの型が同じであることを表現したい任意の2つの型が同じであることを表現したい • 型パラメータを持った値は扱えない • メソッドなら型パラメータを持てる × implicit val x[A] = new Tag[A, A] ○ implicit deff[A] = new Tag[A, A]
2つの型が同じ場合呼び出し可能な関数 class Tag[A, B] () implicit defequalAllowed[A] = new Tag[A, A] deffunc[A, B](implicit ev: Tag[A, B]) = "OK” scala> func[Int, Int] // → OK scala> func[Int, String] // → コンパイルエラー
インスタンス生成の無駄を省く class Tag[A, B] () valsingletonTag = new Tag[Any, Any] implicit defequalAllowed[A]: Tag[A, A] = singletonTag.asInstanceOf[Tag[A, A]] deffunc[A, B](implicit ev: Tag[A, B]) = "OK" • implicitで呼び出される度にインスタンス生成するのは無駄 • implicitで渡される値は使われないので、型さえあえば、中身はなんでもいい • nullでもOK
2つの型パラメータをとる型のシンタックスシュガー2つの型パラメータをとる型のシンタックスシュガー scala> class Op[A, B] () scala> val x: Op[Int, String] = new Op x: Op[Int,String] = Op@ab612f8 scala> val x: IntOp 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 deftpEquals[A]: A =:= A = singleton_=:=.asInstanceOf[A =:= A] } • [不明] From => To を継承している理由は? • [不明]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 ]