LoginSignup
6
6

More than 5 years have passed since last update.

scala の抽象型メンバ(abstract type member)の実践的な例

Posted at

暇つぶしに書く。前回は例が実践的でなかったし、与太話も多かった。practical な情報を好む practical なプログラマには不評だったことだろう。
手も痛いので、さっくり終わらせる。

例えば、akka stream を利用した抽象的なインターフェースを書きたいとする。モチベーションは、実運用時の実装とテスト時等で実装を差し替えたいため、とかでいいだろう。実装を差し替えたい理由で一番多いのは、恐らくテストだ。
閑話休題。早速書いてみる。

trait XXXSources {
  def allXXX: Source[XXX, NotUsed]
}

これは良くない。rigid だ。Materialized value が NotUsed で固定されてしまっている。実装によっては NotUsed では困るかもしれない。
そのため、普通はこうする。

trait XXXSources[Mat] {
  def allXXX: Source[XXX, Mat]
}

これで良くなった。型パラメータは便利だ。

では次のような例ではどうか。所謂リポジトリパターンで、ソート済みのクエリ結果を返すメソッドシグネチャを考える。折角なので akka stream を例に用い続けよう。特に意味はない。

trait Repository[Entity, Mat] {
  def sorted(): Source[Entity, Mat]
}

普通だ。この後、実装していくうちに、とある実装で昇順降順を指定できることが分かり、それに合わせてシグネチャを変更したくなったとする。

trait Repository[Entity, Mat] {
  def sorted(ord: some.implement.Ord): Source[Entity, Mat]
}

特定の実装に依存したシグネチャはよくない。先の例のように型パラメタを使うことで解決できないか。

trait Repository[Entity, Ord, Mat] {
  def sorted(ord: Ord): Source[Entity, Mat]
}

一見うまくみえるが、先ほどよりうまくない。使う側で、実装詳細の型に触れなくてはならない。

class RepositoryImpl[XXXEntity, some.implement.Ord, Future[Done]] { ... }

val repository = new RepositoryImpl
repository.sorted(some.implment.Ord.Asc) // oops

これでは抽象も何もあったものではない。
こういった時、つまり実装詳細の隠蔽をしたい時に抽象型メンバ(abstract type member)は輝く。

trait Repository[Entity, Mat] {
  type Ord
  def asc: Ord
  def desc: Ord
  def sorted(ord: Ord): Source[Entity, Mat]
}

こうなる。

class RepositoryImpl[Entity, Mat] {
  type Ord = some.implement.Ord
  def asc: Ord = some.implement.Ord.Asc
  def desc: Ord = some.implement.Ord.Desc
  def sorted(ord: Ord): Source[Entity, Mat] = ...
}

val repository = new RepositoryImpl
repository.sorted(repository.asc)

これで使う側は Ord がどのような型か知る必要はなくなった。どのような型か知らないため、生成することもできないために、メンバが増えてしまったが、これは仕方がない。一度隠した上で、見せたい部分だけ見せる、ということだ。

抽象型メンバ(abstract type member)は、実践的には Win32 API の HANDLE 型や、opaque pointer のような物だ。使う側には何の情報も与えず、実装を隠蔽する。これによりモジュール間を疎結合にしたり、実装の差し替えを容易にしたりする。

隠す必要がない時には型パラメタ、隠す必要があるときには抽象型メンバを、と覚えていれば大体それで良い。

ところで akka stream の Materialized value は、所謂実装詳細に属するものだ。一方で、利用者が Flow を制御するためのものという側面もある。結果として、抽象型メンバよりは、型パラメータにしておくのが望ましい。何事にも例外はある。

勿論抽象型メンバにしておくこともできるのだが、少々ややこしくなってしまう。どのようにややこしくなってしまうのか気になる人は、自分で書いてみよう。手が疲れただけといえば、その通りだ。

おしまい。

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