Kotlinで一部の実装を移譲して残りを無名クラスで実装したい

結構悩んだのでメモ。

やり方

interface A { fun aaa() }

interface B { fun bbb() }

interface AB : A, B

class AImpl { override fun aaa() { println("aaa") } }

fun makeAdHocAB() { 
  return object : A by AImpl, B {
    override fun bbb() { println("bbb") }
  }
}

みたいなことをしたい。つまり、AとBのどちらも使用できるインタフェースABがあって、ここでAの実装は既存のものを使用するが、Bはアドホックに実装したいという場合。

このコードはby以降でコンパイルエラーになる。正しくはこう。

interface A { fun aaa() }

interface B { fun bbb() }

interface AB : A, B

class AImpl { override fun aaa() { println("aaa") } }

fun makeAdHocAB() { 
  return object : A by AImpl(), B object: B {
    override fun bbb() { println("bbb") }
  } { }
}

何が私は間違っていたのか

Kotlinの委譲はbyの次に型じゃなくてオブジェクトが来なくてはならないシンタックスになっている。Scalaのmix-inを想像していると間違いやすい。

なんでbyの次は型ではなくてオブジェクトにしたいのかと言うと、

class AB(parameter: String, bImpli: B) : A by AImpl(parameter), B by bImpli 

みたいな書き方もしたいから、っぽい。「継承」や「ミックスイン」ではなくて「委譲」なので、委譲する先はインスタンスということなんだろう、多分。

Scalaの場合

ABに自分型を使ったクラスでやる場合はこう。

trait A {
  def aaa()
}

trait B {
  def bbb()
}

trait AImpl extends A {
  override def aaa(): Unit = {
    println("aaa")
  }
}

class AB {
  self: A with B =>
}

val a = new AB with AImpl with B {
  override def bbb(): Unit = { println("bbb") }
}

traitでやりたい場合はABの定義をこうする。

trait AB extends A with B

考察というか雑談

あるインタフェースA, B, C... があってそれぞれの実装を任意に組み合わせた新しいクラスT(もしくはTのインスタンス)を作りたいという時、どっちのアプローチが良いんだろう。色々考えたんだけど、どれもこれも変わらない気がしてきた。

Kotlinの委譲という仕組みはコンストラクタからのDIをもうちょっとオシャレな感じにやりたいのだ、というように考えると分かりやすいかも。ただ、Scalaの自分型と違ってDIされたものを隠蔽できるわけではない。

クラスの定義をするのにインスタンスを指定するというのがちょっと個人的には違和感を感じるのだが…。これはコンストラクタで受け取った引数を渡したいということを意図したものであるとは思うが、クラスの定義に予め決まった状態のインスタンスを渡してしまう事も出来る。こういう事が必要なケースってあるだろうか?(冒頭に上げたコードはクラス定義がほしいのではなくてA, Bを実装したインスタンスが欲しいのであり、クラス定義は再利用しないので要らない)

オブジェクト指向の考え方的にも、「クラスの実装をインスタンスに委譲する」というのはなんか気持ち悪い気もする。どう考えるのが正なんだろう。

Kotlinは悪くない言語だと思うのだが、どうもScalaも打っている身からすると「Scalaとの差異を出すためにあえて最適な言語仕様から外して設計している」と思えてしまう事がある。whenなんかが特にそうだ。

Kotlinの立ち位置的にはJavaからの乗り換えを想定しているのだろうが、現状の言語仕様ではむしろScalaの方がJavaに近い気さえする。「Javaからの乗り換えをしやすくするためにKotlinは矯正されたScalaを目指しますよ」と宣言してしまってScalaに積極的に似せていった言語を作っても良かったと思うのだが…。まぁでも誰であれ、一から言語作るならオリジナルを作りたいよね。

Javaから乗り換えるのに適した言語は個人的には言語仕様だけ見たらC#な気がする。Javaと言語仕様が非常に似ているし、新しい言語仕様もふんだんに盛り込まれて進化している。でもエコシステム含めて考えたら、いくらC#にMonoがあっても乗り換えることは不可能だろう。C#にオープンソースでデファクトっぽいWebフレームワーク等が無いのも問題だ。結局、C#とJavaは守備範囲が違うということなんだろうか。