Blog
RubyistがScalaを勉強した話(続き)
前の記事で、ScalaでRubyistがよく使っていたActiveRecordパターンを実現しました。
今回は私がよく使っているDuck Typingについて話させて頂きます。
Duck Typing とは
“If it walks like a duck and quacks like a duck, it must be a duck”
(もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである)
Rubyでは、Duck Typingを使って、「インターフェースを渡す」ことが多いです。
例えば:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class FilePrinter def print(stream) #ファイルとして出力 end end class PdfPrinter def print(stream) #pdfとして出力 end end class User attr_accessor :printer def use_printer(_) printer = _ end def print printer.print(file) end end |
このコードを見て分かる通り、printという「インターフェース」が存在するものであれば、なんでもUserに渡せます!
ここでDuck Typingはどこが良いかというと、メソッド間で渡すものが「何」じゃなくて、「何ができる」を重視するところです。
つまり、型の依存性を無くして、コードの疎結合を実現できると考えられます。
「せっかく型言語なのに、型の依存性がなくなるのではよくないぞ!」と思っていた方もいらっしゃるかもしれないですが、よく考えてみると、関数型の意味では「処理を重視する」ためのものなので、Duck Typingは悪くないでしょう。
Martin Odeskyのインタビュー記事で、Martin氏がDuck Typingについて
Translated, if it has the features that I want, then I can just treat it as if it is the real thing.
(言い換えるならば、もし実装したい機能があるのであれば、その機能として実装することができる)
つまり、Duck Typingにより、Scalaの表現力を高めることできると書かれています。
ScalaでDuck Typingの実現
Structural type
なんと凄い簡単にScalaでDuck Typingを実現することができます!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
object DSP { def advertise(value: String) = { value.toUpperCase } } object SSP { def advertise(value: String) = { value.toLowerCase } } object DMP { def advertise(value: String) = { "データは最高" } } object Main2 { def advertiseWith(anything: {def advertise(value: String): String}) { println (anything.advertise("AdTech")) } def main(args: Array[String]): Unit = { advertiseWith(DSP) advertiseWith(SSP) advertiseWith(DMP) } } |
上のコードを見ると、advertise
があるものであれば、なんでも受け取るadvertiseWith
メソッドがあります。
そのメソッドに渡すものにより、処理を変えることができます!すごいですね!
メソッドを持っていないものを渡してみると
1 2 3 4 5 6 7 8 |
object CantAdvertise { } object Main2 { ... def main(args: Array[String]): Unit = { advertiseWith(CantAdvertise) } } |
ちゃんとコンパイル時に発見できました。
1 2 3 4 5 6 |
Error:(32, 19) type mismatch; found : ducktyping.CantAdvertise.type required: AnyRef{def advertise(value: String): String} advertiseWith(CantAdvertise) ^ |
さすが便利ですね、Type SafeのDuck Typingは最高
Pimp my Library
上の例を見て、Duck Typingのどこが有用かと考えてみると、Javaでよく使われているStrategy パターンの実現がすごい楽になります。
「インターフェースからオブジェクトを認識する」という意味だと、インターフェースをパラメータとして渡すだけではなくて、Scalaでは [Pimp My Library](http://www.artima.com/weblogs/viewpost.jsp?thread=179766)という面白いパターンがあります。
そのパターンでは、オブジェクトのインターフェースを用いて、implicit conversionを行ったら、既存のクラスにメソッドを追加することができます。Duck Typingとはちょっと別の概念ですが、「インターフェースから対象オブジェクトを認識できる」という意味だと関連性があると考えられますので、紹介させていただきます。
まずは以下のコードをご覧ください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package ducktyping object MyExtensions { implicit def doubleInt(i: Int) = new { def double = i * 2 } } object Main2 { import MyExtensions._ val one = 1 def main(args: Array[String]): Unit = { println("1 * 2 = " + one.double) } } |
コードを読むだけで多分何を意味するかをわかると思います。
implicitメソッド+ structural typeの組み合わせで、既にあるクラスを自由にメソッドを追加することができます!
上の例だと Intのタイプに doubleのメソッドを追加することができます。
何か分かりづらいなと思う方もいらっしゃると思いますが、実際はScalaコンパイラが勝手に
1 |
one.double |
から
1 |
(new MyExtension.doubleInt(1)).double |
に変換してくれるだけです!
ここで何が嬉しいかというと
– レガシーコードを触らなくても良くて、別のモジュールにレガシーモジュールの機能を追加することができます
– Mixinが簡単にでき、デザインパターンでよく言われている:「Composition over inheritance」を簡単に実現できます
やっぱりScalaはすごいFlexibleな言語ですね!
パフォーマンス面
これで十分Structural Typeの便利さを理解して頂いたと思いますが、Structural Typeの大きいデメリットが一つ存在します。それはパフォーマンスです。
なぜStructural Typeがパフォーマンスに影響を簡単に説明すると:reflectionを使うからです。
簡単なベンチマークを取ってみました。
ベンチマークの対象ですが、一つのサンプルでDuck Typingを使って、残りのサンプルは普通にtraitを使います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
object Main2 { //duck typing使う object DSP { def advertise(value: String) = { value.toUpperCase } } trait DSP2 { def advertise(value: String) = { value.toUpperCase } } object DSP2Impl extends DSP2 { } def time(block: => Unit) { val start = java.lang.System.nanoTime() block val time = java.lang.System.nanoTime() - start print("time: " + time/1e6 + " ms") } //duck Typing 使う def advertiseWithDuck(anything: {def advertise(value: String): String}) { anything.advertise("AdTech") } //duck typing使わない def advertiseWithoutDuck(advertiser: DSP2) { advertiser.advertise("Adtech") } def main(args: Array[String]): Unit = { time { for (i <- 0 to 1000000) advertiseWithDuck(DSP) } print("\n") time { for (i <- 0 to 1000000) advertiseWithoutDuck(DSP2Impl) } } } |
結果は。。。
1 2 |
time: 375.038197 ms #duck typing time: 86.880719 ms #trait |
なんと凄い結果。。
こんなにパフォーマンスが悪いことが分かりますが、じゃ、
– 設計ミスで、既存で、触りたくないライブラリに機能追加したい
– パフォーマンスがそんなに重要じゃないところ、あるいは、ボトルネックじゃないところ
つまり、「設計」対「パフォーマンス」のトレードオフをよく考慮して、structural typeを使うべきかと考えます。
Martin Odesky氏の論文にもstructural typeのパフォーマンス周り色々な場面からベンチマークを行ったので、
興味がある方は是非ご参考ください。
http://infoscience.epfl.ch/record/138931/files/2009_structural.pdf
次回には、scalaのオペレーターオーバライドを用いて、なにか面白いプログラミングを作りたいと思いますので、
是非お楽しみにしてください。
Copyright about logo
Copyright (C) 2008 合同会社Rubyアソシエーション
Copyright (c) 2002-2015 EPFL
Author