この記事はぷりぷりあぷりけーしょんず Advent Calendar 2019の5日目の記事です。
はじめに
遅刻しました。ごめんなさい。
書こうと思っていたネタの説明用に Scala のサンプルコードを書いていたら言語仕様を勘違いしていたため間に合いませんでした。
なので、急遽私が何を勘違いしていたかのネタで記事を書きます。
ちなみに普段の仕事は最近『データアーキテクト』と呼ばれているようなことをしています。
が、まだ浸透していない言葉なので『データエンジニア』を自称しています。
勘違い①『兄弟トレイトを複数ミックスした場合、明示的に指定した型の方のメソッド以外は見えなくなると思っていた』
サンプルコード見ないとわかりにくですね。
package com.puripuri.example
trait ParentTrait {
def hoge(): Unit
}
trait ChildTraitA extends ParentTrait {
override def hoge(): Unit = {
methodA()
}
def methodA(): Unit
}
trait ChildTraitB extends ParentTrait {
override def hoge(): Unit = {
methodB()
}
def methodB(): Unit
}
case class ImplA() extends ChildTraitB with ChildTraitA {
def methodA(): Unit = {
println("TraitA")
}
def methodB(): Unit = {
println("TraitB")
}
}
case class ImplB() extends ChildTraitA with ChildTraitB {
def methodA(): Unit = {
println("TraitA")
}
def methodB(): Unit = {
println("TraitB")
}
}
object Main extends App {
ImplA().hoge() // TraitA
ImplB().hoge() // TraitB
val a: ChildTraitA = ImplA()
val b: ChildTraitB = ImplA()
a.hoge() // TraitA
b.hoge() // TraitB になると思っていたら TraitA だった
}
サンプルコードの ChildTraitA
と ChildTraitB
は同じ ParentTrait
を親に持ちます。
ChildTraitA と ChildTraitB の両方のトレイトをクラスにミックスインした場合、『線形化1』の仕組みにより後からミックスインしたトレイトのメソッドが有効になります。サンプルコードの場合の hoge()
メソッドのことです。
というところまでは理解していました。
ただし、インスタンス化する際に型を明示的にした場合は明示的に指定した型が持つメソッドが有効になる。
と思っていたのですがこれは勘違いでした。
実際はインスタンス化の際に指定した型の種類を問わず線形化のルールが適用されます。
サンプルコードの実行結果は以下の通り。
TraitA
TraitB
TraitA
TraitA
一番下の実行結果は ChildTraitB 型でインスタンス化したので methodB() のみが見えるようになって結果は "TraitB" と表示されるものと思っていました。
勘違い②『型パラメータを持つトレイトも普通に複数ミックスインできると思っていた』
こちらもまずはサンプルコードをペタリ
package com.puripuri.example
trait ParentTrait[A] {
def hoge(): A
}
trait ChildTraitA extends ParentTrait[String] {
override def hoge(): String = {
methodA()
}
def methodA(): String
}
trait ChildTraitB extends ParentTrait[Int] {
override def hoge(): Int = {
methodB()
}
def methodB(): Int
}
case class ImplA() extends ChildTraitB with ChildTraitA {
def methodA(): String = {
"TraitA"
}
def methodB(): Int = {
12345
}
}
case class ImplB() extends ChildTraitA with ChildTraitB {
def methodA(): String = {
"TraitA"
}
def methodB(): Int = {
12345
}
}
object Main2 extends App {
println(ImplA().hoge())
println(ImplA().hoge().getClass) // String だと思ってた
println(ImplB().hoge())
println(ImplB().hoge().getClass) // Int だと思ってた
val a: ChildTraitA = ImplA()
val b: ChildTraitB = ImplA()
println(a.hoge())
println(a.hoge().getClass) // String だと思ってた
println(b.hoge())
println(b.hoge().getClass) // ①の勘違いをしていたので Int だと思ってた
}
こちらのコードは①の勘違いに付随する形で勘違いしていたものです。
このコードでは私は線形化の仕組みにより ImplA().hoge()
は String を返し、ImplB().hoge()
は Int を返すものと思っていました。
しかし、このコードは以下の 2 つのメッセージが出てコンパイルエラーとなります。
Error:(23, 12) illegal inheritance;
class ImplA inherits different type instances of trait ParentTrait:
com.puripuri.example.ParentTrait[String] and com.puripuri.example.ParentTrait[Int]
case class ImplA() extends ChildTraitB with ChildTraitA {
Error:(33, 12) illegal inheritance;
class ImplB inherits different type instances of trait ParentTrait:
com.puripuri.example.ParentTrait[Int] and com.puripuri.example.ParentTrait[String]
case class ImplB() extends ChildTraitA with ChildTraitB {
これは完全に勘違いしていました。
型パラメータの型が違う場合はそもそも同時にミックスインできない仕様のようです。
海外でも全く同じ落とし穴にハマっている方がいるようでした。(10 年前に……)
https://www.scala-lang.org/old/node/3423
型パラメータが同じならミックスイン可能
ただし、型パラメータで指定した型が全く同じであればミックスイン可能です。
package com.puripuri.example
trait ParentTrait[A] {
def hoge(): A
}
trait ChildTraitA extends ParentTrait[String] {
override def hoge(): String = {
methodA()
}
def methodA(): String
}
trait ChildTraitB extends ParentTrait[String] {
override def hoge(): String = {
methodB()
}
def methodB(): String
}
case class ImplA() extends ChildTraitB with ChildTraitA {
def methodA(): String = {
"TraitA"
}
def methodB(): String = {
"TraitB"
}
}
case class ImplB() extends ChildTraitA with ChildTraitB {
def methodA(): String = {
"TraitA"
}
def methodB(): String = {
"TraitB"
}
}
object Main3 extends App {
println(ImplA().hoge())
println(ImplA().hoge().getClass)
println(ImplB().hoge())
println(ImplB().hoge().getClass)
val a: ChildTraitA = ImplA()
val b: ChildTraitB = ImplA()
println(a.hoge())
println(a.hoge().getClass)
println(b.hoge())
println(b.hoge().getClass)
}
このコードの実行結果は以下の通りです。
TraitA
class java.lang.String
TraitB
class java.lang.String
TraitA
class java.lang.String
TraitA
class java.lang.String
①のときと同じように線形化されています。
まとめ
雰囲気エンジニアなので普段そこまで技術を突き詰めませんが、せっかくのアドベントカレンダーなので色々と検証コードを書いてみました。
Scala を 1 年以上使ってきて言語仕様を『完全に理解2』しているものの使っていて『何も分からない2』と日々思っています。
が、便利な機能がたくさんあり Java のライブラリなども基本的にそのまま使用可能なのでちゃんと使えるようになれば書いていて楽しい言語だと思います。
最近流行の関数型のパラダイムも含んだ言語ですし。
言語仕様の複雑さとマルチパラダイムという性質の関係でプログラミング初心者には全くオススメできない言語ですが第二言語としては良いんじゃないかなーと思います。
私が遅刻した関係で既に投稿されていますが 6 日目は @ya_mori さんの記事です。