Hatena::ブログ(Diary)

einblickerの日記 このページをアンテナに追加 RSSフィード

2011-12-03

「ランタイムのチェックを一度で済ます」を存在型で

| 23:33 | 「ランタイムのチェックを一度で済ます」を存在型でを含むブックマーク 「ランタイムのチェックを一度で済ます」を存在型でのブックマークコメント

型レベルString - いじわるだねっ

ランタイムのチェックを一度で済ます - EAGLE 雑記

こういうテクニック面白い。

ただ、ランタイムチェックを一度で済ませるというだけなら、依存型っぽい物を作らなくても存在型を使えば似たようなことが出来そうだなーと思ったのでやってみました。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の方を書きなおしてみました。

sameLength — Gist

追記2:

今更ながら推移関係が成り立つ版も書いてみた。

sameLength(4) — Gist

けどScalaの存在型が変な挙動をするのでちょっと微妙な書き方になってしまった。恐らくScalaでは存在型が必要な場面では代わりに抽象メンバーとパス依存型を使うことが推奨されているのだと思われる。のでそのことを後日記事に纏めたい…。

eagletmteagletmt 2011/12/04 05:34 このような sameLength, zipWith の実装で,例えば

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 も同じ長さ」といったものを型レベルで表現できているのかというような話なんですが.

einblickereinblicker 2011/12/04 09:08 eagletmtさん、こんにちは。
前者は書けると思いますが、後者は無理だと思います。同じ長さかどうかの比較が一度に行える状況じゃないと使えないです。
ああ、この記事のコードだと推移関係が成り立つっぽく書いてしまっていますね…。これは私の間違いで、実際には成り立っていないです。申し訳ないです…。

eagletmteagletmt 2011/12/05 00:16 おぉー,そうなんですか.前者は OK な点が Scala を知らない自分にはちょっと興味深いです.
ありがとうございました.

einblickereinblicker 2011/12/07 06:20 亀レスですが、誤解を招く表現だった気がするのでちょっと補足させてください(もう見ておられないかもしれませんが…)。
この記事のコードでやっていることは
「存在型を使ってある特定の操作しか行えないようにする+その操作はリストの長さを変えないものだけに限定する」
ということで、その特定の操作の中でリストの長さを変えるような動作をしていないかどうかについては
値レベルでの確認が必要で、型レベルでの保証はありません。なのでScala固有の何か凄いテクニックを使っているとか、
そういうことではありません。もしその点で誤解させてしまっていたらすいません…。

トラックバック - http://d.hatena.ne.jp/einblicker/20111203/1322922802