You are not allowed to perform this action.

Clear message
Locked History Actions

scala/structuralSubtyping

構造的部分型(structural subtyping)

むつかしそうな名前だが、やりたいことはきわめて簡単。 そもそもプログラミングを簡単にしたいがための仕組みなのだから。

やりたいこと

例えば、Javaでは以下のようには記述できない。

class Greeter {
  public void hello() {
    System.out.println("hello, world");
  }
}
class Executor {
  public void execute(Object someObject) {
    someObject.hello(); // Objectにそんなメソッドはない!コンパイルエラー
  }
}
...
new Executor(new Greeter()).execute();

つまり、Executor.executeメソッドは「任意の」オブジェクトを受け入れ、そのhelloメソッドを呼び出したいわけだが、コンパイラはコンパイル時にパラメータの型を調べて、それにhelloというメソッドがなければエラーにしてしまう。実行時にはhelloというメソッドの定義されたGreeterオブジェクトが渡されているにも関わらずである。

これはJavaなどの静的型付けの言語ではいたし方ないし、これがあるがゆえに実行速度が高いわけである。つまり、実行時に渡されたオブジェクトにそのようなメソッドがあるかどうかを調べなくて済むわけである。コンパイル時にメソッドの有無を判定済みだからである(もちろんリフレクションを用いて調べる場合は別)。

これに対して、ruby等の動的な言語は実行時にメソッドの有無を調べる。それがゆえにどうしても実行速度は上がらないし、完全なテスト(テストカバレッジ100%)が必要になる。例えば、スペルミスして「hell」などという呼び出しを記述してしまえば、そのミスはその行が実際に呼び出されるまではわからない。

※私見だが、これがゆえにスクリプト言語のプログラマは「スペルミスをしないように」長い名前を避ける傾向があるように思われる。しかし、短い簡単な名前ばかりつけていけば、大規模なプログラムでは同じ名前がそこかしこに存在することになり、リファクタリング時には非常な問題になる。「この名前とあの名前は同じだが、同じものを指しているのかわからない」という状況になりうる。静的に決定することができないのだ。スクリプト言語が大規模プログラム作成に向いていない最も大きな理由である。

やり方

さて、Javaで上述のようなことをしたいならば、どうしても以下のように書く必要がある。

interface IHello {
  public void hello();
}
class Greeter implements IHello {
  public void hello() {
    System.out.println("hello, world");
  }
}
class Executor {
  public void execute(IHello someObject) {
    someObject.hello(); // IHelloにはhelloというメソッドがある。OK!
  }
}
...
new Executor(new Greeter()).execute();

説明不要だろうが、ここで重要なことはGreeterとExecutorが共に参照しなければならないIHelloというインターフェースの存在である。これが二つのクラスの架け橋となっている。

これに対して、Scalaでは以下のように書けてしまうのである。

class Greeter {
  def hello { println("hello, world") }
}
class Executor {
  type HelloType = { def hello }
  def execute(someObject: HelloType) {
    someObject.hello
  }
}
new Executor().execute(new Greeter)

つまり、Greeter側は何らのインターフェース(trait)も実装していないし、Executor側も何らのGreeterに関する知識はない。この二つのあいだには、何の架け橋となるものがない。

その代わり、Executorは単に自分の中で「helloというメソッドを持つ型」を宣言し、それを引数として受け入れるだけなのである。そして、executeメソッドは、この型のオブジェクト、つまりhelloというメソッドを持つ型を受け入れるというわけである。

ダックタイピング

こういったもの(こと)をダックタイピングと呼ぶらしく、rubyやsmalltalk界隈でよく使われているらしい。が、この言葉は誤解を招きかねない。 Executer側は単に「メソッドを呼び出しているだけ」であり、何も分類(タイピング)などしていない。 しかも、rubyやsmalltalk等の動的言語では(おそらく)そのメソッドが事前に呼び出し可能かどうかを知ることはできない。 呼び出せれば御の字であるが、呼び出せなければ何らかのエラー処理をするしかないわけで、これではとても「分類」などとは呼べない (PHPでも可能だが、ひどいことにPHPではメソッドを呼び出せない場合、処理がストップしてしまいエラー処理も行うことができない)。

ともあれ、Scalaではこれらの「実行するまではどうなるかわからない」危険な言語とは異なり、安全な「ダックタイピング」を実現していると言える。と、こんなことを書くとかなりの批判が出てきそうだが、批判のできる方はこれらの言語をつかっても問題の起こらない「天才」なのである。平均的なプログラマが大きなシステムの一部を担う場合、このような言語を使用すれば非常な問題が出てくることは否定できない事実であろう。

ダックタイピングについては以下を参照のこと。