search term:

Scala Implicits: 型クラス、襲来

Debasish Ghosh さん (@debasishg) の “Scala Implicits : Type Classes Here I Come” を翻訳しました. 元記事はこちら: http://debasishg.blogspot.com/2010/06/scala-implicits-type-classes-here-i.html (翻訳の公開は本人より許諾済みです) 翻訳の間違い等があれば遠慮なくご指摘ください.

先日 Twitter 上で Daniel と Scala での型クラスについて論議していると,突然このトピックに関する書きかけだった記事を発見した.これを読んでもあなたは特に目新しい事を発見するわけではないが,型クラスに基づいた思考はあなたの設計の幅に価値を与えることができると思う.この記事を書き始めたのはしばらく前に設計の直交性についての記事 (原文)を公開したときのことだ.

まずは GoF の Adapter パターンから始めよう.委譲型の Adapter はよく勧められる合成(composition)というテクニック用いて抽象体(abstraction)1どうしをバインドする.

設計の直交性のときと同じ例を使うと,

case class Address(no: Int, street: String, city: String, 
  state: String, zip: String)

これを LabelMaker というインターフェイスに適合させたいとする.つまり,我々は Address オブジェクトを LabelMaker として使いたい.

trait LabelMaker[T] {
  def toLabel(value: T): String
}

インターフェイス変換を行うアダプターは…

// Adapter クラス
case class AddressLabelMaker extends LabelMaker[Address] {
  def toLabel(address: Address) = {
    import address._
    "%d %s, %s, %s - %s".format(no, street, city, state, zip)
  }
}

// この Adapter は Address オブジェクトに LabelMaker のインターフェイスを提供する.
AddressLabelMaker().toLabel(Address(100, "Monroe Street", "Denver", "CO", "80231"))

さて,上の設計で我々が副次的に導入してしまった複雑さはなんだろう?

Scala 型クラスへのリファクタリング

Debasish Ghosh さん (@debasishg) の “Refactoring into Scala Type Classes” を翻訳しました. 元記事はこちら: http://debasishg.blogspot.com/2010/07/refactoring-into-scala-type-classes.html (翻訳の公開は本人より許諾済みです) 翻訳の間違い等があれば遠慮なくご指摘ください.

二週間ほど前に Scala の暗黙の(implicit)パラメータを用いた型クラスの実装について書いた.型クラスはある抽象体(abstraction)についての直交した関心事を,抽象体そのものに直接組み込むことなくモデル化することができる.これでコアな抽象体から余計なものを取り去って,別々の独立したクラス構造に変えていくことができる.最近 Akka actor のシリアライゼーションをリファクタリングして型クラスの恩恵に関する実地的な知見を得ることができたので,ここに報告したい.

最初は継承と trait でうまくいくと思った…

… しかし,それは長続きしなかった.Jonas Boner と筆者の間で actor のシリアライゼーションに関して面白い論議があり,以下のような設計が生まれた …

trait SerializableActor extends Actor 
trait StatelessSerializableActor extends SerializableActor

trait StatefulSerializerSerializableActor extends SerializableActor {
  val serializer: Serializer
  //..
}

trait StatefulWrappedSerializableActor extends SerializableActor {
  def toBinary: Array[Byte]
  def fromBinary(bytes: Array[Byte])
}

// .. 以下続く

このような trait はシリアライゼーションという関心事をコアな actor の実装と結合(couple)させすぎてしまう.様々なシリアライズ可能な actor があるため,良いクラス名が足りなくなってきていた.GoF本が教えてくれる知恵の一つにインターフェイスを用いたクラスの命名に困るとしたら,間違ったことをやっている,というものがある.関心事をより意味のある方法で分割する別のやり方を探ろう.

型クラスだ

コアの actor 抽象体からシリアライゼーションに関するコードを抜き出し,別の型クラスにした.

/**
 * Actor 直列化のための型クラス定義
 */
trait FromBinary[T <: Actor] {
  def fromBinary(bytes: Array[Byte], act: T): T
}

trait ToBinary[T <: Actor] {
  def toBinary(t: T): Array[Byte]
}

// クライアントはそれぞれの Actor のための Format[] を実装する必要がある
trait Format[T <: Actor] extends FromBinary[T] with ToBinary[T]

actor をシリアライズ可能にするためにクライアントが実装する必要がある FromBinary[T <: Actor]ToBinary[T <: Actor] という二つの型クラスを定義した.これをさらに Format[T <: Actor] という二つを合わせた trait に組み合わせた.