2012年2月17日金曜日

type Self


に対し、りりかるろじかるさんが反応をくれたので書いてみます。

最初に、これが何の役に立つかですが、

trait Foo {
type Self <: Foo
val i: Int
def f(i: Int): Self
def g(i: Int) = Option(f(i))
}
case class Bar(i: Int) extends Foo {
type Self = Bar
def f(i: Int) = Bar(i)
def h = "hoge"
}
view raw self1.scala hosted with ❤ by GitHub

と定義したとき、

val bar: Bar = Bar(100)
assert(bar == Bar(100))
val foo: Bar = bar.f(10)
assert(foo == Bar(10))
val foobar: Option[Bar] = foo.g(1)
assert(foobar == Option(Bar(1)))
val baz: String = foo.h
assert(baz == "hoge")
view raw self2.scala hosted with ❤ by GitHub

となります。

ポイントは、
  • Barからfを呼び出すとBarが返る。
  • Barからgを呼び出すとOption[Bar]が返る。
  • fの返り値からhを呼び出せる。
といったところでしょうか。

つまり、スーパークラスでサブクラスの値を返すことができるということです。

しかし、これにはサブクラスがいちいちSelfを定義しなければいけないので楽したいですよね。
そこで、りりろじさんに考えて頂いた方法を書いていきます。

this.type

this.typeを使って定義すると、

trait Foo {
val i: Int
def f(i: Int): this.type
def g(i: Int) = Option(f(i))
}
case class Bar(i: Int) extends Foo {
type Self = Bar
def f(i: Int) = Bar(i).asInstanceOf[this.type]
def h = "hoge"
}
view raw thistype1.scala hosted with ❤ by GitHub

となります。

Bar.this.typeが要求されているところでBarを渡すとコンパイルエラーがでます。
なので、asInstanceOfを使って定義します。

val bar: Bar = Bar(100)
assert(bar == Bar(100))
val foo: Bar = bar.f(10)
assert(foo == Bar(10))
/*
* scala> val foobar: Option[Bar] = foo.g(1)
* <console>:12: error: type mismatch;
* found : Option[Foo]
* required: Option[Bar]
* val foobar: Option[Bar] = foo.g(1)
* ^
*/
val baz: String = foo.h
assert(baz == "hoge")
view raw thistype2.scala hosted with ❤ by GitHub

なんかエラー出てる・・・・
うーん、これはどういうことでしょう?
gの返り値をOption[this.type]と明示的に書いても変わりません。
むむむむむ・・・・・えらいひと、教えて下さい。

型パラメータ

Genericsまんせー
ということで書いてみる。

trait Foo[A <: Foo[_]] {
val i: Int
def f(i: Int): A
def g(i: Int) = Option(f(i))
}
case class Bar(i: Int) extends Foo[Bar] {
def f(i: Int) = Bar(i)
def h = "hoge"
}
val bar: Bar = Bar(100)
assert(bar == Bar(100))
val foo: Bar = bar.f(10)
assert(foo == Bar(10))
val foobar: Option[Bar] = foo.g(1)
assert(foobar == Option(Bar(1)))
val baz: String = foo.h
assert(baz == "hoge")
view raw generics1.scala hosted with ❤ by GitHub

おお、ちゃんと動きますね。

この型パラメータ版とtype Self版で比べてみると、
  • 型パラメータ有無
  • どちらも、型の情報がなくなると意味がない
  • 継承元がtype Selfを書くか、型パラメータに型を書くか
などあります。

型パラメータを使ったほうが楽に書けそうですが、type Selfを使うと楽になることもあります。

trait Foo {
type Self <: Foo
val i: Int
def f(i: Int): Self
}
trait Baz { self: Foo =>
def g(i: Int) = Option(f(i))
}
case class Bar(i: Int) extends Foo with Baz {
type Self = Bar
def f(i: Int) = Bar(i)
}
view raw self3.scala hosted with ❤ by GitHub

このように、複数のtraitでSelfを使う場合はこの書き方が有利なようです。


他にもっといい方法があったら是非教えてくださいねー!

0 件のコメント:

コメントを投稿