Scala流ダックタイピング
rubyの例。
def execute_with_log(f) puts "start" f.execute() puts "end" end
って定義しておけば、引数にはexecuteメソッドを持つクラスのオブジェクトはなんでも渡せる。
Javaではこれは実現できなくて、引数で宣言した型(もしくはインタフェース)のオブジェクトか、そのサブクラスに限定される。
ScalaではStructural Typeという方法でコンパイラの恩恵を受けながらもダックタイピングが可能になる。
class X { def exe(callable: { def call(num:Int):Unit; def call2(msg:String):String }, num:Int) = { callable.call(num) println(callable.call2("duckTyping!")) } }
exeメソッドはInt引数を取って戻り値無しのcallという名前のメソッドと、String引数を取って戻り値Stringのcall2という名前のメソッドを持つクラスならなんでも受け取ることが出来る。
では実行。
class A { def call(n:Int) = { println("A" * n) } def call2(msg:String):String = "%s in A".format(msg) } class B extends A { override def call(n:Int) = println("B" * n) override def call2(msg:String):String = "%s in B".format(msg) } class C { def call(n:Int) = println("C" * n) def call2(msg:String):String = "%s in C".format(msg) } class D {} def main(args: Array[String]):Unit = { val a = new A() val b = new B() val c = new C() val d = new D() val x = new X() x.exe(a, 1) x.exe(b, 2) x.exe(c, 3) //x.exe(d) // コンパイルエラー。 }
exe引数のところ、持つべきメソッドが複数あると書くのが大変になってくるので別に定義しておくとスマートになります。
type Callable = { def call(num:Int):Unit def call2(msg:String):String } class X { def exe(callable: Callable, num: Int) = { callable.call(num) println(callable.call2("duckTyping!")) } }
DuckTypingって動的型付け言語特有の機能かと思っていましたが、静的型付け言語の恩恵に預かりながら安全に実現できるって素敵です。