型クラス パターンを使用する Scala プロジェクトに取り組んでいるときに、言語がパターンを実装する方法に関して深刻な問題と思われる問題に遭遇しました。Scala 型クラスの実装は、言語ではなくプログラマーが管理する必要があるため、型クラスに属する型は、その型クラスの実装が取り込まれない限り、親型として注釈を付けることはできません。
この点を説明するために、簡単なサンプル プログラムをコーディングしました。会社のさまざまな種類の従業員を処理し、進捗状況に関するレポートを出力できるプログラムを作成しようとしていると想像してください。これを Scala の型クラス パターンで解決するには、次のようにします。
abstract class Employee
class Packer(boxesPacked: Int, cratesPacked: Int) extends Employee
class Shipper(trucksShipped: Int) extends Employee
さまざまな種類の従業員をモデル化した、非常に単純なクラス階層。次に、ReportMaker 型クラスを実装します。
trait ReportMaker[T] {
def printReport(t: T): Unit
}
implicit object PackerReportMaker extends ReportMaker[Packer] {
def printReport(p: Packer) { println(p.boxesPacked + p.cratesPacked) }
}
implicit object ShipperReportMaker extends ReportMaker[Shipper] {
def printReport(s: Shipper) { println(s.trucksShipped) }
}
これで、次のような Roster クラスを作成できるようになりました。
class Roster {
private var employees: List[Employee] = List()
def reportAndAdd[T <: Employee](e: T)(implicit rm: ReportMaker[T]) {
rm.printReport(e)
employees = employees :+ e
}
}
したがって、これは機能します。ここで、型クラスのおかげで、packer オブジェクトまたは shipper オブジェクトを reportAndAdd メソッドに渡すことができ、レポートが出力され、従業員が名簿に追加されます。ただし、名簿内のすべての従業員のレポートを印刷しようとするメソッドを作成するには、reportAndAdd に渡される rm オブジェクトを明示的に保存する必要があります。
このパターンをサポートする他の 2 つの言語、Haskell と Clojure は、この問題を扱っているため、この問題を共有していません。Haskell はデータ型から実装へのマッピングをグローバルに格納するため、常に変数と一緒にあり、Clojure は基本的に同じことを行います。Clojure で完璧に動作する簡単な例を次に示します。
(defprotocol Reporter
(report [this] "Produce a string report of the object."))
(defrecord Packer [boxes-packed crates-packed]
Reporter
(report [this] (str (+ (:boxes-packed this) (:crates-packed this)))))
(defrecord Shipper [trucks-shipped]
Reporter
(report [this] (str (:trucks-shipped this))))
(defn report-roster [roster]
(dorun (map #(println (report %)) roster)))
(def steve (Packer. 10 5))
(def billy (Shipper. 5))
(def roster [steve billy])
(report-roster roster)
従業員リストを型 List[(Employee, ReportMaker[Employee]) に変換するという厄介な解決策とは別に、Scala はこの問題を解決する方法を提供していますか? もしそうでなければ、Scala ライブラリーは Type-Classes を広範囲に使用しているのに、なぜ対処されていないのでしょうか?