LoginSignup
9
8

More than 5 years have passed since last update.

Scala初学者のためのテクニック集(Duck Typing)

Posted at

コップ本を読み終わって、いざそれを実際の開発で応用しようとしてもなかなか難しいですよね。
そんなとき、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引数に注目してください。

{}の中にCsvFormatterSpaceFormatterが持つ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の紹介でした。

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8