Locked History Actions

Diff for "scala/structuralSubtyping"

Differences between revisions 3 and 4
Deletions are marked like this. Additions are marked like this.
Line 16: Line 16:
class Executer { class Executor {
Line 22: Line 22:
new Executer(new Greeter()).execute(); new Executor(new Greeter()).execute();
Line 25: Line 25:
つまり、Executer.executeメソッドは「任意の」オブジェクトを受け入れ、そのhelloメソッドを呼び出したいわけだが、コンパイラはコンパイル時にパラメータの型を調べて、それにhelloというメソッドがなければエラーにしてしまう。実行時にはhelloというメソッドの定義されたGreeterオブジェクトが渡されているにも関わらずである。 つまり、Executor.executeメソッドは「任意の」オブジェクトを受け入れ、そのhelloメソッドを呼び出したいわけだが、コンパイラはコンパイル時にパラメータの型を調べて、それにhelloというメソッドがなければエラーにしてしまう。実行時にはhelloというメソッドの定義されたGreeterオブジェクトが渡されているにも関わらずである。
Line 32: Line 32:

== やり方 ==
Line 44: Line 46:
class Executer { class Executor {
Line 50: Line 52:
new Executer(new Greeter()).execute(); new Executor(new Greeter()).execute();
Line 52: Line 54:

説明不要だろう。これに対して、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は単に自分の中で

構造的部分型(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();

説明不要だろう。これに対して、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は単に自分の中で