0Pricing
Scala for Backend Engineering & Functional Programming · レッスン

Union型とIntersection型

新しい型機能

「Union型とIntersection型」はCoddyKit上の無料Scala for Backend Engineering & Functional Programmingレッスンです。 これはレッスン4/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはScala for Backend Engineering & Functional Programming学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。

新しい型の機能

Scala 3では、型システムにユニオン型(A | B)と交差型(A & B)が追加されています。継承階層を作らなくても、値をより正確に表現できます。

object Main:
  def main(args: Array[String]): Unit =
    println("Union: A | B, Intersection: A & B")

ユニオン型の基本

ユニオン型 A | Bは、AまたはBのいずれかである値を表します。型に対する集合論的なORです。

object Main:
  def show(x: Int | String): String = x match
    case i: Int    => s"int $i"
    case s: String => s"str $s"

  def main(args: Array[String]): Unit =
    println(show(42))
    println(show("hi"))

ユニオン型に共通のスーパータイプは不要

Scala 2のEitherとは異なり、ユニオン型ではラッパーは必要ありません。共通の親クラスを持たない任意の2つの型をユニオンにできます。

object Main:
  def parse(input: Int | Boolean): String =
    input match
      case n: Int     => s"number $n"
      case b: Boolean => s"flag $b"

  def main(args: Array[String]): Unit =
    println(parse(7))
    println(parse(true))

エラーのモデル化にユニオン型を使う

ユニオン型は、ラッパー型を使わずに軽量なエラー結果を表す場合に便利です。enumやシングルトンと組み合わせると、コードを簡潔に記述できます。

case object NotFound

object Main:
  def lookup(id: Int): String | NotFound.type =
    if id == 1 then "Alice" else NotFound

  def main(args: Array[String]): Unit =
    println(lookup(1))
    println(lookup(2))

交差型の基本

交差型 A & Bは、AでもありBでもある値を表します。型に対する集合論的なANDであり、traitの組み合わせによく使われます。

trait Named:
  def name: String
trait Aged:
  def age: Int

object Main:
  def describe(p: Named & Aged): String =
    s"${p.name} is ${p.age}"

  def main(args: Array[String]): Unit =
    val person = new Named with Aged:
      def name = "Bob"
      def age = 40
    println(describe(person))

交差型は可換的

交差型では順序は関係ありません。A & BはB & Aと同じ型です。これはScala 2の複合型A with Bとは異なり、後者はメンバー解決の一部で順序の影響を受けます。

trait Readable:
  def read: String
trait Writable:
  def write(s: String): Unit

object Main:
  def use(r: Readable & Writable): Unit =
    r.write("x")
    println(r.read)

  def main(args: Array[String]): Unit =
    val rw = new Readable with Writable:
      def read = "data"
      def write(s: String) = println(s"wrote $s")
    use(rw)

複数のtraitを組み合わせる

3つ以上の型を交差させることもできます。その結果には、列挙したすべての機能が必要です。

trait A: def a: Int
trait B: def b: Int
trait C: def c: Int

object Main:
  def sum(x: A & B & C): Int = x.a + x.b + x.c

  def main(args: Array[String]): Unit =
    val v = new A with B with C:
      def a = 1
      def b = 2
      def c = 3
    println(sum(v))

ユニオン型の型エイリアス

長いユニオン型にはtypeエイリアスで名前を付け、シグネチャを読みやすくできます。

type Json = Int | String | Boolean

object Main:
  def render(j: Json): String = j match
    case i: Int     => i.toString
    case s: String  => s"\"$s\""
    case b: Boolean => b.toString

  def main(args: Array[String]): Unit =
    println(render("hi"))
    println(render(true))

ユニオン型の網羅的なマッチング

コンパイラーはユニオンのメンバーを把握しているため、マッチングがすべてのケースを網羅しているか確認できます。すべての選択肢を処理すれば、そのmatchは全域的になります。

type Shape = "circle" | "square"

object Main:
  def sides(s: Shape): Int = s match
    case "circle" => 0
    case "square" => 4

  def main(args: Array[String]): Unit =
    println(sides("square"))

ユニオン型の拡張

ユニオン型の値を型推論される変数に代入すると、Scalaはユニオン型を維持します。ただし、一部の操作では最小上界へ拡張されるため、ユニオン型を維持したい場合は型注釈を付けてください。

object Main:
  def pick(flag: Boolean): Int | String =
    if flag then 1 else "one"

  def main(args: Array[String]): Unit =
    val x: Int | String = pick(false)
    println(x)

使い分け

目的に合った機能を選びましょう。

  • ユニオン:値が複数の型のいずれかである(OR)。
  • 交差:値が複数のtraitを同時に満たす(AND)。
  • ユニオンはラッパー型を、交差型は深い継承を避けるのに役立ちます。
trait Logger: def log(s: String): Unit
trait Clock:  def now: Long

object Main:
  def run(env: Logger & Clock): Unit =
    env.log(s"time=${env.now}")

  def main(args: Array[String]): Unit =
    val env = new Logger with Clock:
      def log(s: String) = println(s)
      def now = 100L
    run(env)

理解度チェック

ユニオン型と交差型についての理解度を確認しましょう。

まとめ

Scala 3のユニオン型と交差型について学びました。

  • A | B:値はAまたはBで、ラッパーも共通の親も必要ありません。
  • A & B:値はAでもBでもあり、可換的です。
  • パターンマッチングでユニオン型を絞り込み、交差型でtraitを組み合わせます。
  • typeエイリアスで長いユニオン型に名前を付けます。
object Main:
  def length(x: String | List[Int]): Int = x match
    case s: String    => s.length
    case l: List[Int] => l.length

  def main(args: Array[String]): Unit =
    println(length("hello"))
    println(length(List(1, 2, 3)))

よくある質問

「Union型とIntersection型」レッスンは無料ですか?

はい。「Union型とIntersection型」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Scala for Backend Engineering & Functional Programmingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Scala for Backend Engineering & Functional Programmingコースには全4レッスンが含まれています。

「Union型とIntersection型」で何を学びますか?

新しい型機能 ブラウザで直接実行するハンズオンコードでScala for Backend Engineering & Functional Programmingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Scala for Backend Engineering & Functional Programmingを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのScala for Backend Engineering & Functional Programmingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン4/4です。

「Union型とIntersection型」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このScala for Backend Engineering & Functional Programmingレッスンでコードを書いて実行できますか?

はい。すべてのScala for Backend Engineering & Functional Programmingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. 重要なインデント
  2. Scala 3 の enum
  3. Opaque Types
  4. Union型とIntersection型
← Scala for Backend Engineering & Functional Programmingに戻る