はじめに
君を退屈から救いに来たんだ♪ nikkieです。
YAPC::Hiroshima 2024で聞いた伊藤直也さんのトークから気づきを1本したためます。
タグ付きユニオン、完全に理解したと思う!
目次
- はじめに
- 目次
- (再演) 関数型プログラミングと型システムのメンタルモデル(YAPC::Hiroshima 2024)
- 『プロを目指す人のためのTypeScript入門』
- 『ロバストPython』
- Pydanticのドキュメント
- 終わりに
- P.S. ちょうぜつ本のコラムより
- P.S. やめ太郎さんのこの記事もタグ付きユニオンのことやないか!
(再演) 関数型プログラミングと型システムのメンタルモデル(YAPC::Hiroshima 2024)
伊藤 直也(@naoya_ito)さんのトークがスタートしています!
— yapcjapan (@yapcjapan) 2024年2月10日
「(再演) 関数型プログラミングと型システムのメンタルモデル」#yapc_i#yapcjapan pic.twitter.com/cqNrWc0yIW
気づきが多々ある素晴らしいトークでした1。
その中から以下を取り上げます(このスライドは初演のものと思われます)。
直和(OR)を使うことで、仕様上あり得ない状態を表現せずに済んでいます!(Making illegal states unrepresentable)
型の直和という概念、すでにインプットしていた知識と結びつきました。
『プロを目指す人のためのTypeScript入門』
Forkwell Libraryより。
著者のうひょさんによる基調講演で、タグ付きユニオンなる概念を聞きました。
タグ付きユニオンとは
「タグ」を見れば判別できるユニオン型
『ロバストPython』
ここまではTypeScriptの話ですが、Pythonにおいても同様の解説が存在します。
それが『ロバストPython』!(箇所は4.2です)
データクラスを例に、型の直積と直和を比較して説明されます。
直積で表す例:
# ref: https://github.com/pviafore/RobustPython/blob/dafb95d801dff2c8ff7856ba46d3c052d54e0033/code_examples/chapter4/product_type.py @dataclass class Snack: name: str condiments: set[str] error_code: int disposed_of: bool Snack("Hotdog", {"Mustard", "Ketchup"}, 5, False)
詳細は書籍を見ていただきたいのですが、取りうる値は
- nameが3通り
- condimentsが4通り
- error_codeが6通り
- disposed_ofが2通り
すなわち144通りです。
問題は、これらの状態が全部有効だとは限らないことだ。
「error_codeが0でない場合にdisposed_ofをTrueにしたい」といった制約があります。
直積で表すと、仕様上あり得ない状態を表現してしまっているということですね。
直和で表す例を見ましょう
# ref: https://github.com/pviafore/RobustPython/blob/dafb95d801dff2c8ff7856ba46d3c052d54e0033/code_examples/chapter4/sum_type.py @dataclass class Error: error_code: int disposed_of: bool @dataclass class Snack: name: str condiments: set[str] snack: Union[Snack, Error] = Snack("Hotdog", {"Mustard", "Ketchup"})
SnackかErrorのUnion!
snackはSnack(nameとcondimentsだけ)かError(エラーコードと論理値だけ)になる。
144あった取りうる値は、Snackが3*4
で12通り、Errorが5*2
で10通り。
合計22通りまで減りました!(お見事)
組合せ爆発していない! めっちゃ助かりますね🙌
Union[Snack, Error]
という型ヒントは、きもち、Result型なるものにつながるようにも見えます。
Pydanticのドキュメント
Pydanticのドキュメントにも見つけました。
https://docs.pydantic.dev/2.6/concepts/unions/#discriminated-unions
Cat、Dog、Lizardの3つのクラスがあり、いずれかのインスタンスを持つ形でModelが定義されます
class Model(BaseModel): pet: Union[Cat, Dog, Lizard] = Field(..., discriminator='pet_type') n: int
Field
のdiscriminator
がタグに使う属性名の指定2。
ここではpet_type
属性です。
伊藤さんの資料に戻ると「和で組み合わせて構築したものは、パターンマッチで分解」とあります。
Pydanticのドキュメントのコードを元に手を動かします。
(Python 3.12.0、Pydantic 2.6.1)
タグ付きユニオン、パターンマッチでいい感じに書けた気がする!3
終わりに
YAPC::Hiroshimaの伊藤さんトークをきっかけに、『プロを目指す人のためのTypeScript入門』『ロバストPython』の解説がつながり、タグ付きユニオンを完全に理解しました。
- 型の直和(Union)により、仕様上あり得ない状態を表現せずに済む(組合せ爆発しない)
- タグを見れば判別できる直和が、タグ付きユニオン
- パターンマッチで分解
思うに、直積だと少ない行数で書けるので、魅力的に映ります。
しかしながら、少ない行数ゆえに表現力には限りがあるため、コードを増やした直和でむしろ表現したほうが、長期的に恩恵を受けられるととらえました。
P.S. ちょうぜつ本のコラムより
Tagged Unionsという語はちょうぜつ本で知りました(8章のコラム)。
PHPにTagged Unionsの提案(RFC)がされています(StatusはDraft)
P.S. やめ太郎さんのこの記事もタグ付きユニオンのことやないか!
プロパティとして型名を持たせてあげるの
実装は、パターンマッチで型名による分岐をしていますね(『プロを目指す人のためのTypeScript入門』を参照した記事です)
このエントリの元ツイートです。
伊藤直也さんの #yapcjapan のトーク(※スライドは初演のものですが)
— nikkie / にっきー (@ftnext) 2024年2月12日
『ロバストPython』のUnion型の解説で過去に読んでいた内容。
トーク聞いてUnion型の嬉しさの解像度上がったな〜
無効な状態を表現しない、かつ、コンパイラがそれを理解!https://t.co/UPF54JAAio
- タグ付きユニオン完全理解のほか、「型は集合」が飲み込めました↩
- https://docs.pydantic.dev/2.6/concepts/fields/#discriminator↩
- 『ロバストPython』のPydanticを使わない実装も、パターンマッチで分解すればよいと考えています↩