コップ本を読み終わって、いざそれを実際の開発で応用しようとしてもなかなか難しいですよね。
そんなとき、Monadの世界にどっぷり浸かるのも良いですが、Scalaの表現豊かな言語仕様を活かしたデザインパターンや、コップ本には書かれていないテクニックを学んでみるのも良いかもしれません。
そんなわけで、今回はDuck Typingを紹介します。
(あ、ちなみに私のScalaレベルは3くらいで、次のレベルになるにはあと142の経験値が必要と言われています)
"If it walks like a duck and quacks like a duck, it must be a duck"(もしもそれがアヒルのように歩き、アヒルのように鳴くのなら、それはアヒルである)by Dave Thomas
上記はDuck Typingを端的に表した言葉ですが、
要するに同じシグニチャを持ったオブジェクト同士ならば、宣言的なインターフェイスを実装していなかったとしても同じと見做せるということです。
Duck Typingは動的型付け言語の特徴ともいうべきものですが、静的型付け言語なScalaでも実現が可能です。
それでは、さっそく具体的な例で見てみましょう。
Duck Typingの具体例
解決したいこと
例えば下記のような、シグニチャが全く同じなメソッドを持つ2種類のクラスがあったとします。
class CsvFormatter {
def format(seq: Seq[String]): String = seq.mkString(",")
}
class SpaceFormatter {
def format(seq: Seq[String]): String = seq.mkString(" ")
}
シグニチャは一緒ですが、format
メソッドを持った共通のtrait
をmixinしていたり、
abstract class
を継承しているわけではありません。
この2種類のクラスを下記の第2引数(f: [フォーマッター]
)へ渡したい場合、どうしますか?
def duckTypingFormat(seq: Seq[String], f: [フォーマッター]) = f.format(seq)
Duck Typingはそんな時に使えるテクニックです。
Duck Typingの適用
先ほどのduckTypingFormat
メソッドを以下のように変更します。
def duckTypingFormat(seq: Seq[String], f: {def format(seq: Seq[String]): String}) = f.format(seq)
ここで、第2引数に注目してください。
{}の中にCsvFormatter
とSpaceFormatter
が持つformat
メソッドのシグニチャを書きました。
こうすることで、{}内に定義したメソッドを持つクラスであれば、第2引数へ渡せるようになるのです。
object DuckTypingExample {
def duckTypingFormat(seq: Seq[String], f: {def format(seq: Seq[String]): String}) = f.format(seq)
def main(args: Array[String]): Unit = {
val csvFormatter = new CsvFormatter
val spaceFormatter = new SpaceFormatter
val seq = Seq("a", "b", "c", "d")
println(duckTypingFormat(seq, csvFormatter)) // => a,b,c,d
println(duckTypingFormat(seq, spaceFormatter)) // => a b c d
}
}
静的なDuck Typing
ちなみに先ほどの例で、こんな場合はコンパイルエラーになります。
// メソッド名が合ってない
def duckTypingFormat(seq: Seq[String], f: {def formatformat(seq: Seq[String]): String}) = f.formatformat(seq)
// 引数の型が合ってない
def duckTypingFormat(seq: Seq[String], f: {def format(seq: Seq[Int]): String}) = f.format(seq.map(_.toInt))
// 戻り値の型が合ってない
def duckTypingFormat(seq: Seq[String], f: {def format(seq: Seq[String]): Option[String]}) = f.format(seq)
// 引数の数が合ってない
def duckTypingFormat(seq: Seq[String], f: {def format(seq: Seq[String], value: String): String}) = f.format(seq, "")
このように、ScalaのDuck Typingは静的にコード解析をしてくれるので、
知らない間にシグニチャが変わった場合でもコンパイラがエラーとしてちゃんと教えてくれるのです。
この辺が、実行時に評価される動的型付け言語との違いでしょうか。
そんなわけで今回はDuck Typingの紹介でした。