2011-12-03
「ランタイムのチェックを一度で済ます」を存在型で
Scala | |
![]()
こういうテクニック面白い。
ただ、ランタイムチェックを一度で済ませるというだけなら、依存型っぽい物を作らなくても存在型を使えば似たようなことが出来そうだなーと思ったのでやってみました。Scalaで。
import scalaz._ import Scalaz._ type Stuff = List[Int] case class Thing(id: String, stuff: Stuff) type Result = (A, A, Show[A], Monoid[A]) forSome { type A } def eqIdCheck(x: Thing, y: Thing): Option[Result] = { if (x.id == y.id) Some(( x, y, new Show[Thing] { def show(a: Thing) = a.toString.show }, new Monoid[Thing] { val zero = Thing(x.id, Nil) def append(s1: Thing, s2: => Thing) = Thing("id1", s1.stuff ::: s2.stuff) } )) else None } val result1 = eqIdCheck(Thing("id1", List(1)), Thing("id1", List(2))) result1.get match { case (a, b, s, m) => implicit val (show, monoid) = (s, m) (a |+| b |+| b).println val x = Thing("id1", List(10)) //(a |+| x).println //idが同じでも、まだeqIdCheckでチェックされていないのでコンパイルエラー } val result2 = eqIdCheck(Thing("id2", List(10)), Thing("id2", List(20))) result1.get match { case (a1, b1, s1, m1) => implicit val (show1, monoid1) = (s1, m1) result2.get match { case (a2, b2, s2, m2) => implicit val (show2, monoid2) = (s2, m2) (a1 |+| b1).println (a2 |+| b2).println //(a1 |+| b2).println //異なるeqIdCheckの呼び出しで得られたThingをappendしようとするとコンパイルエラー } }
aとxは異なる型の値だとコンパイラが認識してくれるので、|+|を適用するところでコンパイルエラーにしてくれる! スゴイ! implicit valを一々作らないといけないのがちょっとダサいけれども…。
zipWithの方も同じような感じでできました。
type ZipWith[A[_], T] = { type Apply[U] = ((T, T) => U) => (A[T], A[T]) => A[U] } type Result[T] = (A[T], A[T], Show[A[T]], Forall[ZipWith[A, T]#Apply]) forSome { type A[_] } def sameLength[T](xs: List[T], ys: List[T]): Option[Result[T]] = { if (xs.length == ys.length) Some(( xs, ys, new Show[List[T]]{ def show(a: List[T]) = a.toString.show }, new Forall[ZipWith[List, T]#Apply]{ def apply[U] = (f: (T, T) => U) => (_: List[T]).zip(_: List[T]).map{case (a, b) => f(a, b)} } )) else None } sameLength(List(1, 2), List(3, 4)).get match { case (a1, b1, s1, zip1) => implicit val show1 = s1 sameLength(List(10, 20, 30), List(40, 50, 60)).get match { case (a2, b2, s2, zip2) => implicit val show2 = s2 zip1[Int](_ + _)(a1, b1).println zip2[Int](_ + _)(a2, b2).println //zip1[Int](_ + _)(a1, List(3, 4)).println //これはコンパイルエラー //zip2[Int](_ + _)(a1, b2).println //これもコンパイルエラー } }
やはりimplicit valを書くのがちょっと不細工。出来ればこんなふうに書きたい。
//こう書いたら hoge match { case (foo, implicit bar) => fugafuga } //↓これと同じ意味になる hoge match { case (foo, _bar) => implicit val bar = _bar fugafuga }
Scala2.10 以降に入るかもしれない機能まとめ - scalaとか・・・で紹介されているmacroがあれば出来るのかなぁ。現状でもコンパイラプラグインを使うと出来そうだけれど敷居が高そうな気が。
追記:
わざわざShowクラスのインスタンスを作らなくてもprintln関数の適用はできる(引数がAny型の関数に存在型の値を適用可能)みたいですね…。もうちょっと綺麗に書けそうなので、sameLengthの方を書きなおしてみました。
追記2:
今更ながら推移関係が成り立つ版も書いてみた。
けどScalaの存在型が変な挙動をするのでちょっと微妙な書き方になってしまった。恐らくScalaでは存在型が必要な場面では代わりに抽象メンバーとパス依存型を使うことが推奨されているのだと思われる。のでそのことを後日記事に纏めたい…。
トラックバック - http://d.hatena.ne.jp/einblicker/20111203/1322922802
リンク元
- 62 http://www.google.co.jp/url?sa=t&rct=j&q=動画を準備中&source=web&cd=1&ved=0CC0QFjAA&url=http://d.hatena.ne.jp/einblicker/20110116/1295150825&ei=FFraTvb2BunHmQW2gqzCCw&usg=AFQjCNFwkRaZj1GQIkXvIrXAsnQe6n
- 47 http://www.google.co.jp/url?sa=t&rct=j&q=ニコニコ動画+動画準備中&source=web&cd=6&sqi=2&ved=0CFcQFjAF&url=http://d.hatena.ne.jp/einblicker/20110116/1295150825&ei=UXDaT
- 26 http://www.google.co.jp/url?sa=t&rct=j&q=ニコ動 動画を準備中&source=web&cd=4&sqi=2&ved=0CEcQFjAD&url=http://d.hatena.ne.jp/einblicker/20110116/1295150825&ei=uXDcTv-GDqLNmAW3-Jm-B
- 19 http://www.google.co.jp/url?sa=t&rct=j&q=ハースト指数 フラクタル次元&source=web&cd=9&ved=0CGgQFjAI&url=http://d.hatena.ne.jp/einblicker/20110125%2
- 16 http://search.yahoo.co.jp/search?p=動画を準備中&search.x=1&fr=top_ga1_sa&tid=top_ga1_sa&ei=UTF-8&aq=&oq=
- 15 http://search.yahoo.co.jp/search?p=ニコニコ+動画を準備中&aq=-1&oq=&ei=UTF-8&fr=top_ga1_sa&x=wrt
- 10 http://reader.livedoor.com/reader/
- 10 http://reader.livedoor.com/subscribe/http://d.hatena.ne.jp/einblicker/20111123/1322042879
- 9 http://search.yahoo.co.jp/search?p=ニコ動 準備中&search.x=1&fr=top_ga1_sa&tid=top_ga1_sa&ei=UTF-8&aq=&oq=
- 7 http://www.google.co.jp/url?sa=t&rct=j&q=圏論の基礎&source=web&cd=10&sqi=2&ved=0CGkQFjAJ&url=http://d.hatena.ne.jp/einblicker/20110312/1299926067&ei=EYnaTqiZJc_CtAbF0d3PCw&usg=AFQjCNHBcRJn0WxEDB7rAsI8Mu_jQKPP
case sameLength xs ys of Just ys' -> print (zipWith (,) xs (zipWith (,) xs ys'))
や,
case sameLength xs ys of Just ys' -> case sameLength ys' zs of Just zs' -> print (zipWith (,) xs zs')
といったものに相当する式は書けるのですか?
つまり「zipWith の結果もまた長さ n」や「xs と ys が同じ長さで ys と zs が同じ長さなら xs と zs も同じ長さ」といったものを型レベルで表現できているのかというような話なんですが.
前者は書けると思いますが、後者は無理だと思います。同じ長さかどうかの比較が一度に行える状況じゃないと使えないです。
ああ、この記事のコードだと推移関係が成り立つっぽく書いてしまっていますね…。これは私の間違いで、実際には成り立っていないです。申し訳ないです…。
ありがとうございました.
この記事のコードでやっていることは
「存在型を使ってある特定の操作しか行えないようにする+その操作はリストの長さを変えないものだけに限定する」
ということで、その特定の操作の中でリストの長さを変えるような動作をしていないかどうかについては
値レベルでの確認が必要で、型レベルでの保証はありません。なのでScala固有の何か凄いテクニックを使っているとか、
そういうことではありません。もしその点で誤解させてしまっていたらすいません…。