Scala3の交差点タイプ
1. 概要
交差タイプがScala3に組み込みタイプとして追加されました。
このチュートリアルでは、それらを使用してクラス階層を定義する方法を理解します。
2. 交差型
交差点タイプは複合データタイプです。 同時に異なるデータ型の値を表すために使用されます。 The & 演算子は交差タイプを作成するために使用されます。
最初の交差点タイプの例を一緒に書いてみましょう。
trait Scissors:
def cut: Unit
trait Needle:
def sew: Unit
def fixDressOne(dressFixer: Scissors & Needle) =
dressFixer.cut
dressFixer.sew
はさみとニードルの2つのタイプを、それぞれメソッドで定義しました。 さらに、2つの交点をパラメーターとして使用するメソッドを追加しました。 はさみ &
object DressFixer extends Scissors, Needle {
override def cut = println("Cutting dress")
override def sew = println("Sewing dress")
}
println(fixDressOne(DressFixer)) // prints "Cutting dress Sewing dress "
交差タイプは可換であることに注意することが重要です。 これは、逆署名を使用して2番目のメソッドを作成することで示すことができます。
def fixDressTwo(dressFixer: Needle & Scissors) =
dressFixer.cut
dressFixer.sew
println(fixDressTwo(DressFixer)) // prints "Cutting dress Sewing dress "
fixDressTwo メソッドは、コンパイルエラーなしでDressFixerオブジェクトを受け取ることができます。 はさみと針と同じタイプです針とはさみ 。
2.1. オーバーライドされたメソッドは呼び出しを注文します
交差タイプは可換であることがわかりました。 ただし、型の順序は型の動作に何らかの影響を及ぼしますか?
例を考えてみましょう:
trait OneGenerator:
def generate: Int = 1
trait TwoGenerator:
def generate: Int = 2
object NumberGenerator21 extends OneGenerator, TwoGenerator {
override def generate = super.generate
}
object NumberGenerator12 extends TwoGenerator, OneGenerator {
override def generate = super.generate
}
def generateNumbers(generator: OneGenerator & TwoGenerator) =
generator.generate
generateNumbers(NumberGenerator21)および generateNumbers(NumberGenerator12)の結果はどうなりますか?
調べるためにいくつかのテストを書いてみましょう:
class BasicIntersectionTypeTest extends AnyWordSpec with Matchers {
"Intersection Types" should {
"use linearization to decide method override" in {
generateNumbers(NumberGenerator21) shouldBe (2)
generateNumbers(NumberGenerator12) shouldBe (1)
}
}
}
にもかかわらず OneGenerator&TwoGenerator と同じタイプであること TwoGenerator& OneGenerator、 オーバーライドされたメソッドの動作は異なる場合があります。 線形化、 右から左への順序付け、呼び出すメソッドをオーバーライドするかどうかを決定します。
3. 交差点タイプの代替
交差点タイプは、複数のデータ型であるオブジェクトを定義する簡潔な方法です。 ただし、Scalaでこれを行う方法は他にもあります。
異なる構成とパターンを使用して同じコードを書き直し、違いを分析します。
3.1. ダックタイピング
交差点タイプを使用して別の例の作成を開始しましょう。
trait Scissors:
def cut: Unit
trait Knife:
def cut: Unit
trait Chainsaw:
def cut: Unit
object PaperCutter extends Knife, Scissors {
override def cut = print("Cutting stuff")
}
def cutPaper(pc: Knife & Scissors) =
pc.cut
ナイフとはさみは一枚の紙を切るのに間違いなく適しています、そしてこれは交差タイプでモデル化するのが簡単です。
DuckTypingを使用して同じものを再実装します。
class Scissors() {
def cut(): Unit = { println("Cutting with Scissors") }
val canCutPaper: Boolean = true
}
class Knife() {
def cut(): Unit = { println("Cutting with Knife") }
val canCutPaper: Boolean = true
}
class Chainsaw() {
def cut(): Unit = { println("Cutting with Chainsaw") }
}
type PaperCutter = {
val canCutPaper: Boolean
def cut(): Unit
}
def cutPaper(pc: PaperCutter) =
pc.cut()
この単純な例でも、ボイラープレートがどれだけ必要かがはっきりとわかります。 アクションを実行できるクラスと実行できないクラスを区別するために、フィールド canCutPaper、を持つ新しいタイプを導入する必要がありました。
必ずしもそこに属していないロジックを使用して、クラスのサイズを増やしています。
3.2. 過負荷
オーバーロードでは、追加するタイプごとに同じメソッドを実装する必要があります。ここで、オーバーロードを使用して前の段落の例を実装できます。
class Knife()
class Scissors()
def cutPaper(cutter: Knife) = println("Cutting with Knife")
def cutPaper(cutter: Scissors) = println("Cutting with Scissors")
新しいタイプを追加するたびに、cutPaperメソッドを追加する必要があります。 これには明らかに、交差点タイプよりも多くの定型文とコードの重複が必要です。
3.3. 継承
継承を使用して、元の例と同じ目標を達成できます。 ただし、ヘルパーtraitを追加する必要があります。
それを実装して、どのように見えるかを見てみましょう。
trait Scissors:
def cut: Unit
trait Needle:
def sew: Unit
trait Tools extends Scissors, Needle
object DressFixer extends Tools {
override def cut = print("Cutting dress ")
override def sew = print("Sewing dress ")
}
def fixDress(dressFixer: Tools) =
dressFixer.cut
dressFixer.sew
より大きなコードベースでの階層への追加クラスの追加は、必ずしも正当化されるとは限りません。さらに、外部ライブラリを使用する場合は、追加できない場合もあります。
4. 結論
このチュートリアルでは、交差タイプを使用してデータモデルを形成する方法を学習しました。
また、この組み込みデータ型の代替案も見て、交差点型がScala3によって提供される新しいツールである方法を示しました。