「合併型」のご紹介:Scala 3の新しい型表現(2)

Scala 3.3.4
最終更新:2020年7月9日

[AD] Scalaアプリケーションの開発・保守は合同会社ミルクソフトにお任せください

この記事では、Scala 3(コードネーム:Dotty)で新たに導入された「合併型(Union Types)」について解説します。

合併型とは「AまたはB」を表す型

合併型とは、二つの型の性質のいずれかを満たすような値のもつ型を表します。

|記号で表します。

例えばAという型とBという型があるとき、A | Bと表すと「Aである、もしくはBである型」という意味になります。

例えば以下のようなコードを書くことができます。

case class UserId(value: Int) extends AnyVal case class Email(value: String) extends AnyVal case class User() def findUser(id: UserId | Email): User = { id match { case UserId(value) => lookupById(value) case Email(value) => lookupByEmail(value) } }

合併型は可換的で結合的

合併型は可換的です。
つまり、A | BB | A と同じ型となります。

また、結合的でもあります。
つまり、A | (B | C)(A | B) | Cと同じ型となります。

合併型は交差型と組み合わせると分配的

交差型は&記号を使用し、例えば型Aと型Bについて「AかつB」といった関係性を表現します。

合併型は交差型と組み合わせることにより、分配的になります。

A & (B | C) は、A & B | A & Cと同じ型となります。

計算の優先順位は&の方が高く、|の方が低いので、このように表記することができます。

合併型は交差型と対をなす概念なので、交差型についても合わせて押さえておくと良いでしょう。

内部構造までは引き継がない

以下のコードはコンパイルが通りません。
というのも、A | Bにはdef run: Resultというメソッドを持たないからです。

trait A { def run: Result } trait B { def run: Result } def execute(x: A | B) = x.run

たとえ両者に共通するメンバが存在していたとしても、合併した型にこれが引き継がれることはないということです。

これに対して、以下のコンパイルは通ります。

trait C { def run: Result } trait A extends C trait B extends C def execute(x: A | B) = x.run

ABはいずれもCのサブクラスなので、A | BCのサブクラスです。
したがってrunメソッドを持っている、というわけです。


備考

本記事では"union types"に対する訳語として「合併型」を採用しています。
「直和型」「合併型」の違いについては以下のリンクを参考としています。

サイト内検索