41
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

継承によらないポリモーフィズム実現手法

Last updated at Posted at 2016-12-04

ポリモーフィズム(polymorphism; 多態, 多相)」というとオブジェクト指向プログラミングにおける継承(インターフェースの継承)による実現手法がよく知られている。
しかし、ポリモーフィズムを実現するための手法はオブジェクト指向的な型階層によるものに限らず様々なものがある。
ここでは、Expression Problemに対する解決策として挙げられる、継承によらないポリモーフィズムの代表的な実現手法として、

  • 型クラス(type class)
  • プロトコル(protocol)
  • マルチメソッド(multimethod)

による実装を複数言語で試してみた。
いずれの方法も明示的な継承関係を必要とせず、拡張に対して開いているため、既存の型(例えば言語組み込み型)に対しても容易に拡張可能なことが特徴的で、ある種のインターフェース定義に対する実装を追加することで、いつでも適用範囲を拡大できる。

0. 継承(inheritance; cf. subtyping)

Java

cf. Lesson: Interfaces and Inheritance (The Java™ Tutorials > Learning the Java Language)

インターフェース定義

public interface WhoAmI {
  String identify();
}

インターフェース実装

// クラス定義
public class Complex implements WhoAmI {
  private final double real;
  private final double imag;
  public Complex(double real, double imag) {
    this.real = real;
    this.imag = imag;
  }
  public double getReal() {
    return real;
  }
  public double getImag() {
    return imag;
  }
  @Override
  public String toString() {  // pretty print用に設定
    return String.format("Complex(%s, %s)", real, imag);
  }
  // 新たに定義する型に対して実装
  @Override
  public String identify() {
    return String.format("私はComplexの%sです。", this);
  }
}

// 既存の型に対して実装するのは直接的にはできない(Expression Problem)

インターフェース利用

public class Who {
  public static void ask(WhoAmI who) {
    System.out.println("あなたは誰? ―― " + who.identify());
  }
}

> Who.ask(new Complex(42.0, 0.0))
あなたは誰? ―― 私はComplexのComplex(42.0, 0.0)です

1. 型クラス(type class)

Haskell

cf. A Gentle Introduction to Haskell: Classes

インターフェース定義

class WhoAmI a where
    identify :: a -> String

インターフェース実装

-- データ型定義
data Complex = Complex
    { real :: !Double
    , imag :: !Double
    } deriving Show
complex :: Complex  -- デフォルト値設定
complex = Complex { real = 0.0, imag = 0.0 }

-- 新たに定義した型に対して実装
instance WhoAmI Complex where
    identify x = "私はComplexの" ++ show x ++ "です。"

-- 既存の型に対して実装
instance WhoAmI Integer where
    identify x = "あたしはIntegerの" ++ show x ++ "だよ〜。"
instance WhoAmI Double where
    identify x = "うちはDoubleの" ++ show x ++ "っ!"

インターフェース利用

ask :: WhoAmI a => a -> IO ()
ask who = putStrLn $ "あなたは誰? ―― " ++ identify who

> ask complex { real = 42.0 }
あなたは誰? ―― 私はComplexComplex {real = 42.0, imag = 0.0}です。
> ask 42
あなたは誰? ―― あたしはInteger42だよ〜。
> ask 42.0
あなたは誰? ―― うちはDouble42.0!

Scala

cf. What are Scala context bounds? | Scala Documentation

インターフェース定義

trait WhoAmI[A] {
  def identify(x: A): String
}
object WhoAmI {
  def identify[A: WhoAmI](x: A): String = implicitly[WhoAmI[A]].identify(x)
  // または、明示的にimplicit parameterを利用して
  // def identify[A](x: A)(implicit who: WhoAmI[A]): String = who.identify(x)
}

インターフェース実装

// ケースクラス定義
case class Complex(real: Double = 0.0, imag: Double = 0.0)

// 新たに定義した型に対して実装
implicit object ComplexWhoAmI extends WhoAmI[Complex] {
  def identify(x: Complex): String = s"私はComplexの${x}です。"
}

// 既存の型に対して実装
implicit object IntWhoAmI extends WhoAmI[Int] {
  def identify(x: Int): String = s"あたしはIntの${x}だよ〜。"
}
implicit object DoubleWhoAmI extends WhoAmI[Double] {
  def identify(x: Double): String = s"うちはDoubleの${x}っ!"
}

インターフェース利用

def ask[A: WhoAmI](who: A): Unit = println("あなたは誰? ―― " + WhoAmI.identify(who))

> ask(Complex(real = 42.0))
あなたは誰? ―― 私はComplexのComplex(42.0,0.0)です
> ask(42)
あなたは誰? ―― あたしはIntの42だよ〜。
> ask(42.0)
あなたは誰? ―― うちはDoubleの42.0!

2. プロトコル(protocol)

Clojure

cf. Clojure - Protocols

インターフェース定義

(defprotocol WhoAmI
  (identify [x]))

インターフェース実装

;; レコード定義
(defrecord Complex [real imag])
(defn complex [& {:keys [real imag]  ; デフォルト値設定
                  :or {real 0.0, imag 0.0}}]
  (->Complex real imag))

;; 新たに定義した型に対して実装
(extend-protocol WhoAmI
  Complex
  (identify [x] (format "私はComplexの%sです。" (pr-str x))))

;; 既存の型に対して実装
(extend-protocol WhoAmI
  Long
  (identify [x] (format "あたしはLongの%sだよ〜。" x))
  Double
  (identify [x] (format "うちはDoubleの%sっ!" x)))

インターフェース利用

(defn ask [who]
  (println (str "あなたは誰? ―― " (identify who))))

> (ask (complex :real 42.0))
あなたは誰? ―― 私はComplex#user.Complex{:real 42.0, :imag 0.0}です。
nil
> (ask 42)
あなたは誰? ―― あたしはLong42だよ〜。
nil
> (ask 42.0)
あなたは誰? ―― うちはDouble42.0!
nil

Elixir

cf. Protocols - Elixir

インターフェース定義

defprotocol WhoAmI do
  def identify(x)
end

インターフェース実装

# 構造体定義
defmodule Complex do
  defstruct real: 0.0, imag: 0.0
end

# 新たに定義した型に対して実装
defimpl WhoAmI, for: Complex do
  def identify(x), do: "私はComplexの#{inspect(x)}です。"
end

# 既存の型に対して実装
defimpl WhoAmI, for: Integer do
  def identify(x), do: "あたしはIntegerの#{x}だよ〜。"
end
defimpl WhoAmI, for: Float do
  def identify(x), do: "うちはFloatの#{x}っ!"
end

インターフェース利用

  defmodule Who do
    def ask(who), do: IO.puts "あなたは誰? ―― " <> WhoAmI.identify(who)
  end

> Who.ask(%Complex{real: 42.0})
あなたは誰? ―― 私はComplex%Complex{imag: 0.0, real: 42.0}です。
:ok
> Who.ask(42)
あなたは誰? ―― あたしはIntegerの42だよ〜。
:ok
> Who.ask(42.0)
あなたは誰? ―― うちはFloat42.0っ!
:ok

3. マルチメソッド(multimethod)/多重ディスパッチ(multiple dispatch)

Clojure

cf. Clojure - Multimethods and Hierarchies

インターフェース定義

(defmulti identify class)

インターフェース実装

;; レコード定義
(defrecord Complex [real imag])
(defn complex [& {:keys [real imag]  ; デフォルト値設定
                  :or {real 0.0, imag 0.0}}]
  (->Complex real imag))

;; 新たに定義した型に対して実装
(defmethod identify Complex [x]
  (format "私はComplexの%sです。" (pr-str x)))

;; 既存の型に対して実装
(defmethod identify Long [x]
  (format "あたしはLongの%sだよ〜。" x))
(defmethod identify Double [x]
  (format "うちはDoubleの%sっ!" x))

インターフェース利用

(defn ask [who]
  (println (str "あなたは誰? ―― " (identify who))))

> (ask (complex :real 42.0))
あなたは誰? ―― 私はComplex#user.Complex{:real 42.0, :imag 0.0}です。
nil
> (ask 42)
あなたは誰? ―― あたしはLong42だよ〜。
nil
> (ask 42.0)
あなたは誰? ―― うちはDouble42.0!
nil

Common Lisp

cf. generic function (総称関数)

インターフェース定義

(defgeneric identify (x))

インターフェース実装

;; クラス定義
(defclass my-complex ()
  ((real
    :initarg :real
    :initform 0.0
    :reader re)
   (imag
    :initarg :imag
    :initform 0.0
    :reader im)))
(defmethod print-object ((x my-complex) stream)  ; pretty print用に設定
  (print-unreadable-object (x stream)
    (format stream "my-complex :real ~a :imag ~a" (re x) (im x))))

;; 新たに定義した型に対して実装
(defmethod identify ((x my-complex))
  (format nil "私はmy-complexの~aです。" x))

;; 既存の型に対して実装
(defmethod identify ((x integer))
  (format nil "あたしはintegerの~aだよ〜。" x))
(defmethod identify ((x float))
  (format nil "うちはfloatの~aっ!" x))

インターフェース利用

(defun ask (who)
  (princ (concatenate 'string "あなたは誰? ―― " (identify who))))

> (ask (make-instance 'my-complex :real 42.0))
あなたは誰? ―― 私はmy-complexの#<my-complex :real 42.0 :imag 0.0>です。
"あなたは誰? ―― 私はmy-complexの#<my-complex :real 42.0 :imag 0.0>です。"
> (ask 42)
あなたは誰? ―― あたしはinteger42だよ〜。
"あなたは誰? ―― あたしはintegerの42だよ〜。"
> (ask 42.0)
あなたは誰? ―― うちはfloat42.0!
"あなたは誰? ―― うちはfloatの42.0っ!"

まとめ

  • 型クラス、プロトコル、マルチメソッドといった言語機能/手法によってもポリモーフィズムは実現できる
  • これらは拡張に対して開いているという点で従来の継承によるポリモーフィズムよりも自由度が高い

Further Reading

41
28
2

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
41
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?