読者です 読者をやめる 読者になる 読者になる

kmizuの日記

プログラミングや形式言語に関係のあることを書いたり書かなかったり。

this.typeとcloneと型安全性

Scala

Scalaでthis.typeとcloneを組み合わせたときに起こる問題について、@cocoa_rutoさんの一連のポストが興味深かったので引用してみる:

Scalaでthis.typeなフィールドを持つオブジェクトをcloneしてもそのフィールドは元のオブジェクトを差す。クラスの型メンバも Outer.this.type#Innerという型を持つのでInnerをフィールドとして持つOuterをcloneすると悲しいことになる。

http://twitter.com/cocoa_ruto/status/6399352940

cloneした後で newOuter.inner = new newOuter.Innerとやる。Innerのサブクラスを作っている場合は、innerの型をOuter#Innerと明示して、Innerに create(o: Outer): Outer#Innerを用意する。

http://twitter.com/cocoa_ruto/status/6399447696

ちなみにJavaでいうところのouter.new Inner()はScalaではnew outer.Innerと書きます。

http://twitter.com/cocoa_ruto/status/6399637997

Scalaの型はJavaの型よりも強いので、Javaのコードを呼び出すと型安全性でなくなるということか。

http://twitter.com/cocoa_ruto/status/6399802258

もうちょっと詳しく言うと、不正なバイトコードを吐くわけじゃない(多分)ので弱健全性は成り立つ(本当は意味論がちゃんと決まらないとなんとも言えないんだけど)。一方でScalaの this.type型はthisとnullのみを持つ集合と定義されてるので、強健全性は成り立たない。

http://twitter.com/cocoa_ruto/status/6401820138

なるほどなるほど。もうちょっと単純な例で言うと、

class X extends Cloneable {
  var myself: this.type = this
  override def clone: X = super.clone.asInstanceOf[X]
}
val x1 = new X
val x2 = x1.clone

があったときに、

x1.myself eq x1

は成り立つけど、

x2.myself eq x2

x2.mysefl eq null

は成り立たない、という話ですね(x2.myselfの型はthis.typeである、つまり、x2自身かnullのどちらかでなければならない事が型システムで定められているのにそれが破られている)。ただ、より正確に言うなら、これは、Javaのメソッドを呼び出しているから、というよりもシャローコピーとthis.type(おそらくもっと一般化してsingleton typeに関しても)の相性が悪いというべきではないでしょうか。

というのは、このケースについて、AnyRef#clone()の処理(=シャローコピー)をScalaレベルのコードで書くと、

val x1 = new X
val x2 = new X
x2.myself = x1.myself

のようになりますが(厳密には違いますが、このケースではそう考えて問題無いはず)、このようなコードは実際にScalaで書くと型チェッカーによってrejectされます。しかし、cloneメソッドが内部で行っている処理はScalaの型チェッカーによるチェックをスキップするため、上記の処理に相当する型安全でない処理がチェックされないまま実行が行われ、結果として型安全ではなくなるわけです。