Scalaにおけるextendとimplicit conversionの類似点・相違点のまとめ

#はじめに

本記事は[scala.rookies #2 @オンライン](https://connpass.com/event/184444/)における発表「ScalaのOption型にはIterable型へのimplicitな型変換が定義されているが、直接継承しないのはなぜか」の詳細です。


Scalaにおいて、あるA型の変数をT型の変数としても扱いたい場合、A型を定義するときにT型を継承(extend)する方法と、A型の変数を暗黙的にT型に変換(implicit conversion)する方法が考えられます。
本記事では、この2つの類似点、相違点についてまとめます。

#概念的な説明
extendとimplicit conversionの違いを図にすると以下のようになります。
![ScalaのImplicit_conversion.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/602298/8806efe6-9bd8-8993-0a12-4229f36f36ce.jpeg)
`X extends Y`の場合は、`X`型が`Y`型でもある場合を表していて、implicit conversionの方は、`X`型の変数で`Y`型のメソッドが呼び出された時に`Y`型へ変換される仕組みです。

ただし、どちらの場合も`X`型の変数で`Y`上で定義されたメソッド`.methodA`を呼び出すと、その変数はあくまで`Y`型として扱われ、`X`型の変数であるという情報を失います。そのため、この2つの違いがわかりづらいと思います。
そこで、具体的なコードを見ながら、その挙動の違いを見ていくことにします。

#準備

具体的なコードで説明するため、実際にextendした型とimplicit conversionを定義した型を作ります。

まず、継承元のクラス`Root`を定義します。

```scala

class Root(var rootVar:Int, val rootVal:Int) {

  def succ2nd : Root = new Root(rootVar, rootVal + 1)
}
```
`Root`の変数として、値の再代入可能な`rootVar`、再代入不能な`rootVal`の2つがあり、メソッド`succ2nd`が定義されています。

それをextendしたクラス`Ext`、implicit conversionが定義されたクラス`Imp`の2つを定義します。

```scala

class Ext(var nVar:Int, val nVal:Int, val other:Int) extends Root(nVar, nVal) {
}

class Imp(var nVar:Int, val nVal:Int, val other:Int) {
}
implicit def ImpToRoot(x:Imp) : Root = new Root(x.nVar, x.nVal)
```

`Ext`も`Imp`のどちら引数`nVar`、`nVal`もそれぞれ`Root`の引数`rootVar`、`rootVal`に対応しているのが分かります。

それぞれの型の変数を用意しておきます。

```scala

var ext = new Ext(1, 2, 3)
var imp = new Imp(1, 2, 3)
```

#類似点
extendとimplicit conversionの類似点をまとめます。

##変換先の型として扱える
extendした場合でも、implicit conversionした場合でも変換先のメソッドが使えます。

上の例だと`ext`、`imp`ともに`Root`型変数として扱えます。

```scala

def idRoot(x:Root) = x

val extRoot = idRoot(ext)
val impRoot = idRoot(imp)
```

##変換先のメソッドが使える
extendした場合でもimplicit conversionした場合でも変換先のメソッドが使えます。

```scala

val ext2 = ext.succ2nd
val imp2 = imp.succ2nd
```

ただし、返り値の型が元の型と同じメソッド、すなわち**演算が定義されていても変換前の型には戻らない**ので注意です。

```scala

println(ext2.getClass)  // = class Root
println(imp2.getClass)  // = class Root
```

今回の例では、単項演算子`succ2nd`は第2引数を1増やすように定義していますが、`Ext`型や`Imp`型で`Root`上で定義された`succ2nd`を使用しても、返り値が`Root`型になり、`Ext`型や`Imp`型に戻りません。
そのため、`Ext`型や`Imp`型でも**変換前の型に戻すような演算を定義するためには、それぞれの型の上でオーバーライドする必要があります。**
これは`Iterable`型の`map`等の関手も、継承した場合でも必ずオーバーライドする必要があるということです。

##型の変換後は元の型には戻れない

変換先の型として扱った後には、元の型には戻れません。

```scala

def extAsRoot : Root = ext
def impAsRoot : Root = imp

println(extAsRoot.nVar) // Error
println(impAsRoot.nVar) // Error
```

上記の例と同様、型変換は基本的に**不可逆**です。これは型変換は一般に単射ではなく、複数の値から同じ要素が作れる可能性があるため元の値が特定できないのが理由です。
今回の例だと、`val ext124 = new Ext(1, 2, 4)`、`val root = new Root(1, 2)`としたとき、`root == idRoot(ext)`かつ`root == idRoot(ext124) `ですが、`ext != ext124`より、`root`になるような元の`Ext`型の値が一意に定まらないことが分かります。


#相違点
extendとimplicit conversionの相違点は以下のようなものです。

##変換先の変数が作られるタイミング
extendした場合とimplicit conversionが定義されている場合とでは、型の内部構造に違いがあります。

extendした型では、変換先、すなわち継承元の変数は変数宣言時に作られ、extendした型の内部変数として保持されます。
上の例では`Ext`型の変数宣言時、すなわち`new Ext()`をした時に、継承元である`Root`の内部変数である`rootVar`や`rootVal`の値が宣言され、`ext`の変数として保持されます。

それに対して、implicit conversionが定義されている場合は、変換先の変数は変換先の型として扱う時に初めて宣言されるため、基本的にその内部には保持されません。
上の例では`Imp`型の変数宣言時、すなわち`new Imp()`をしたタイミングには`Root`型の内部変数に関する代入は行われず、`Imp`型の変数を`Root`型として扱う際に初めて`rootVar`や`rootVal`が宣言されます。

例を使って具体的に違いを見ていきます。


###具体例で説明

もう一度`ext`と`imp`を宣言するところから解説します。

```scala

var ext = new Ext(1, 2, 3) // var ext.rootVar = 1; val ext.rootVal = 2
var imp = new Imp(1, 2, 3)
```

extendした方の`Ext`型では、その変数`ext`の宣言時に同時に`ext.rootVar`と`ext.rootVal`のフィールドが作られます。対して、implicit conversionの方の`Imp`型は宣言時には`Root`型に関する情報は作られません。
したがって、以下のようなコードを実行しても

```scala

ext.nVar = -1 // ext.rootVar is not changed
imp.nVar = -1
```
`ext`宣言時の`nVar`の値を参照していた`ext.rootVar`の値は変化しません。

このとき、それぞれ`Root`型の変数`rootVar`を参照してみると以下のようになります。

```scala

println(ext.rootVar) // = 1
println(imp.rootVar) // = -1
```

`ext`の方は変数宣言時に定義された`rootVar`の値のまま変化しませんが、`imp`の方は、`imp.rootVar`が呼び出された瞬間に`Root`型への変換が行われるため、変更後の`nVar`の値で`rootVar`が定義されます。

一旦、それぞれの`nVar`の値を元に戻し、今度は`rootVar`を変化させてみます。

```scala

ext.nVar = 1
imp.nVar = 1

ext.rootVar = 100
imp.rootVar = 100
```

`ext`の方はすでに宣言時に定義されていた`rootVar`の値が変わりますが、`imp`の方は`imp.rootVar`が呼び出された瞬間に`rootVar`が宣言され、この値を書き換えます。
しかしながら、`imp`の方で書き換えた`rootVar`を参照しようとすると、以下のようなことが起こります。

```scala

println(ext.rootVar) // = 100
println(imp.rootVar) // = 1
```

これは、`imp.rootVar`を呼び出したとき、新たに`Root`型への変換が行われるためで、先ほど書き換えた`rootVar`ではなく、新たに宣言された`rootVar`の値、つまり`imp.nVar`の値になります。
すなわち先ほど書き換えた値は参照することができません。

このように、`var`で宣言した変数を書き換えようとすると、extendで定義した場合とimplicit conversionを定義した場合とでその挙動に違いが出てきます。

ちなみに、extendとimplicit conversionの両方が定義されている場合、以下のようにextendの方が優先されます。

```scala

implicit def ExtToRoot(x:Ext) : Root = new Root(0, 0)

val newExt = new Ext(1, 2, 3)
println(newExt.rootVal) // = 2
```

##implicit conversionでは推移的な型変換ができない

推移的な型変換とは、型A, B, Cに対し、A型がB型に変換でき、B型がC型に変換できる時のA型からC型への自然な型変換のことを指します。

implicit conversion同士による型変換の場合、このA型からC型への型変換をimplicitに実行してくれません。[^1]

[^1]: Scala 2.12の場合

以下の例を見ながら解説します。

```scala

class ExtExt(val mVal:Int) extends Ext(0, mVal, 0) {}
class ImpExt(val mVal:Int) {}
implicit def ImpExtToExt(x:ImpExt) : Ext = new Ext(0, x.mVal, 0)

class ExtImp(val mVal:Int) extends Imp(0, mVal, 0) {}
class ImpImp(val mVal:Int) {}
implicit def ImpImpToImp(x:ImpImp) : Imp = new Imp(0, x.mVal, 0)
```

`Root`型に変換可能な`Ext`型、`Imp`型に対し、それぞれをextendした型、implicit conversionを定義した型を用意します。
(`Imp`型をextendした型は`ExtImp`になります。)

それぞれ4つの型を`Root`型として扱おうとすると

```scala

println(new ExtExt(1).rootVal) // = 1
println(new ImpExt(1).rootVal) // = 1
println(new ExtImp(1).rootVal) // = 1
println(new ImpImp(1).rootVal) // Error
```

implicit conversion先のimplicit conversionが使えないことが分かります。

もちろん、`Root`型に直接implicit conversionするようなものを定義すれば、変換することができます。

```scala

implicit def ImpImpToRoot(x:ImpImp) : Root = ImpToRoot(x)
println(new ImpImp(1).rootVal) // = 1
```


implicit conversionによる暗黙の型変換は、それが連続しているかどうかに関わらず、2回以上は行われない仕様みたいです。

```scala

class ImpExtImp(val lVal:Int) {}
implicit def ImpExtImpToExtImp(x:ImpExtImp) : ExtImp = new ExtImp(x.lval) {}

println(new ImpExtImp(2).rootVal) // Error
```

##implicit conversionを使うと相互参照が可能

相互参照とは、型A、Bに対し、A型はB型としても扱え、B型はA型としても扱える状態のことを言います。

extendの場合、`class A extends B`と`class B extends A`の両方は記述できず、一方向性が成り立っていないと定義できない仕様になっています。
しかしながら、implicit conversionを使えば、相互参照をすることが可能です。

例えば、以下のように記述できます。

```scala

implicit def RootToExt(x:Root) : Ext = new Ext(0, 0, x.rootVar)
implicit def RootToImp(x:Root) : Imp = new Imp(0, 0, x.rootVal)
```

この定義により、`Root`型と`Ext`型、`Root`型と`Imp`型は相互参照することができます。
この例より相互参照は、implicit conversion同士、または片方がimplicit conversionであれば記述することが分かります。

また、相互参照には、再変換時に元の値に戻す制約は特にないです。この例だと、
`Ext(1, 2, 3)`を`Root`型に変換すると`Root(1, 2)`になりますが、これを`Ext`型に再変換すると`Ext(0, 0, 1)`となり、元の値に戻らないことが分かります。
ただ、混乱の元になりかねないので、元の値に戻らない記述はあまりオススメはしません。


#まとめ
Scalaのextendとimplicit conversionの類似点と相違点についてまとめました。この記事が読者の皆様の参考になれば幸いです。