uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

オプショナルタイピングの実効性と陥穽

Groovyにはオプショナルタイピングという概念があって、これは要するに変数宣言、メソッドの返り値や引数の宣言において、型指定を省略する事も、しないこともでき、適切な方を選べるという事です。ささっと書きたいときは省略し、安全性を期待するときは指定する、といった使い分けができます。これは「常に指定しなければならない」もしくは「指定する事が一切できない」にくらべ、良いことだと私は思います。(過去記事)

しかし、型を指定した場合でも、Javaの静的型と同じレベルのチェックを与えてくれるわけではありません。

というのも、Groovyの型指定は、そのコードが実行されないとチェックが実行されないからです。

def foo(InputStream s) {
   ......
}

のように引数sがInputStream型だと宣言し、

foo(3)

などのように異なる型の引数での呼び出しを書いてもコンパイルエラーにはなりません。groovycしても同じです。いつチェックされるかというと、実行時にチェックされます。つまりfoo(3)の呼び出しが実際に実行され、3がsに引数として渡されようとしたときに、型チェックが実行されます。

要するに上は、ごくおおざっぱな意味では、

def foo(s) {
   assert s instanceof InputStream
}

のようなものなのです。

まあ、コンパイルチェックを期待しない方がいいのはGroovyの基本ですから、これはこれ、こういうものとして。

実行時にチェックされるんだったら、ダックタイピングと何が違うの、というと、

  • 同じ実行時でも、少しだけチェックのタイミングが前倒しになる。上の例で言うと、ダックタイピングだとfoo()におけるsに対するメソッド呼び出したとえばs.read()などを呼んだときにエラーが出ますが、オプショナルタイピングだとメソッドを呼んだ瞬間にエラーになる。
  • たまたま存在している同名メソッドを呼び出せてしまう、ということがない
  • エラーメッセージが分かりやすくなる(「メソッドが無い」ではなく「型変換できない」)。
  • バグだった場合の原因箇所の特定がやりやすくなる。型の不一致が生じた箇所の指摘となる
  • 型情報は人間がコードを読むときにドキュメントとして役立つ(2010.5.26.追加しました。)

などですかね。上記はすべて静的型チェックでも得られる利点なので、Groovyのオプショナルタイピングは、ダックタイピングと静的型チェックのちょうど中間的な存在だと言えるのではないでしょうか*1

さてもう一つの留意点は、Groovyでは自動的な変換があって、例えば

String s = 3
int i = "a"

というような文が実行可能で、これを実行するとsには"3"という文字列が、iにはaの文字コード97が代入されます。せっかく型指定してるのにーと思うかもしれませんが、まあそうですね。オプショナルタイピングの利点を損なうケースだと言えます。

ただこれもこういうものです。(キリッ

ちなみに2文字以上の文字列においては、

int a = "ab"

は型の変換ができないというエラーになります。

Caught: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'ab' with class 'java.lang.String' to class 'java.lang.Double'

c.f. flow sensitive typing
http://beta.mybetabook.com/showpage/508402720cf2ffb79bb046db

*1:型チェックのタイミングが実行時かコンパイル時か、ということと、型の一致を「名前一致」で判定するのか、「構造/シグネチャ一致(同名かつシグネチャが一致するメソッドの存在による型一致判定=ダックタイピング)」で判定するかは直交だという事。C++のテンプレートはその意味で裏返しで「コンパイル時ダックタイピング」だといえる。