7
5

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.

Clojureにおけるポリモーフィズム解説

Last updated at Posted at 2019-03-30

Clojureにおけるポリモーフィズム解説

リスプであるClojureではJavaのような型でのOOPとは一味違った形でポリモーフィズムを実現できます。

Clojureではポリモーフィズムのサポートとして

  1. defmulti
  2. protocol

というマクロが用意されています。

defmulti

使い方

(defmulti name docstring? attr-map? dispatch-fn & options)

簡単な例として、いろいろな図形の面積を求める公式をdefmultiを使って書いてみたいと思います。

世の中には三角形や円など、様々な図形があり、どの図形も「面積」を持っています。

面積を求める公式は図形ごとに異なっていますが、全て「面積」ということは共通しているので、一貫した扱いをしたいところ。

ここでは図形の面積を求める関数をareaとして、defmultiを使って実装してみます。

まず、「長方形」と「円」のコンストラクターを用意します。

(defn rectangle [w h] {:width w :height h :id ::rect})

(defn circle [r] {:radius r :id ::circle})

次に、これらの面積を計算する関数areaをdefmultiで宣言しましょう。

(defmulti area (fn [x] (:id x)))

ここで、ディスパッチとして図形の:idを使用していることに注意してください。

例えば、xが上記のrectangleの結果として出た値ならば、(:id x)は::rectとなるので、後ほど定義する::rectに対応したareaのバージョンがそのxに対して適応されることになります。

それでは、実際に、::rectと::circleに対応したareaを実装していきます。

(defmethod area ::rect [x] (* (:width x) (:height x)))

(defmethod area ::circle [x] (* (:radius x) (:radius x) Math/PI))

これで、それぞれの図形にふさわしく面積を求める関数ができました。

それでは、実際に長方形と円の面積をareaで計算してみます。

(def r (rectangle 10 5)) 

(def c (circle 3))

(area r) ;; => 50

(area c) ;; => 28.27 

以下のように、areaに渡せる図形のコレクションから、それらの面積の和を求めることも容易になります。

(defn sum-area [coll] (reduce + (map area coll)))

(sum-area [(rectangle 10 5) (circle 3)]) ;;=> 78.27 (= 50 + 28.27)

プロトコル

defprotocolは他のオブジェクト指向言語でのインターフェースに対応する機能を提供します。つまり、関数の「型」だけ定義し、実装は後回しにします。

上で行ったうような異なる図形の面積計算なども、プロトコルを使って行えます。

そのためには、まず、「面積」が計算できる図形を表すプロトコルを宣言します。

(defprotocol Shape
  (area [this]))

ここでは長方形や円などの具体的な図形をレコードを使って表現してみます。

(defrecord Rectangle [width height]
  Shape
  (area [this] (* width heigt)))

(defrecord Circle [radius]
  Shape
  (area [this] (* radius radius Math/PI)))

レコードのインスタンスを生成するには、下記のように

(クラス名)+ドット

と書くか、newというマクロを使います。

(def r (Rectangle. 5 3)) ;; or (new Rectangle 5 3)

(def c (Circle. 3)) ;; or (new Circle 3)

レコード型はマップのように、キーワードでフィールドを取り出したり、updateなどマップに適用できる関数が使えます。

(:radius c) ;=> 3

(update c :radius inc) ;=>  Circle{:radius 4}

プロトコルの関数として実装されているareaの値もチェックしましょう。

(area r) ;; => 15

(area c) ;; => 28.27

## プロトコルとdefmultiの組み合わせ

プロトコルとdefmultiを組み合わせれば、さらに柔軟なポリモーフィズムが実現できます。

ここでは、例として上で定義した長方形と円に対して相似な拡大を行う

scale

という関数を作ってみます。

この場合、分岐の基準として

class

が役に立つでしょう。この関数は

(class x)

でxのクラスを返します。

例:

(class r) ;;=> Rectangle

それでは、classをディスパッチ関数としてマルチメソッドを作ってみましょう。

(defmulti scale (fn [x s] (class x)))

(defmethod scale Rectangle [x s]
  (-> x (update :width (partial * s)) (update :height (partial * s))))

(defmethod scale Circle [x s]
  (update x :radius (partial * s)))

結果:

(scale r 2) ;;=> Rectangle{:width 10, :height 6}

(scale c 2) ;; => Circle{:radius 6}

無事に相似拡大されていました。

ついでに「面積比は相似比の二乗に比例する」という図形の性質も確かめてみましょう。(一つの事例だけでは「証明」にはなっていませんが、ここではとりあえず関数を使ってみるのが目的です。)

(= (* 2 2  (area c) ) (area (scale c 2))) ;; => true

想定した結果になりました。

まとめ

今日はClojureでポリモーフィズムを担うプロトコルとマルティメソッドについてご紹介しました。この2つの機能を使いこなせば、非常に柔軟にポリモーフィズムを実現できるでしょう。

今回の内容は以上になります。最後までお読みいただきありがとうございました。

7
5
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
7
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?