こんにちは、SaaS Product Teamのasanoです。
業務でF#を使って開発を行ったのですが、そこで判別共用体の便利さを実感したので紹介したいと思います。
判別共用体とは
判別共用体(discriminated unions)とは、F#で使用される型の一種です。
TupleやRecordが型の積集合を表すのに対し、判別共用体は型の和集合を表し、定義したパターンのうちいずれかのパターンを取ることができます。
以下、Microsoft公式ページからの引用です。
判別共用体は、数多くの名前付きケースのうちのいずれかである可能性がある値をサポートします。ケースの値や型が、それぞれ異なる場合もあります。 判別共用体は、異種データ、有効ケースやエラー ケースなどの特殊なケースを持つ可能性のあるデータ、インスタンスごとに型が異なるデータ、小さいオブジェクト階層に対する代替手段などの場合に役立ちます。 さらに、再帰的な判別共用体は、ツリー データ構造を表すために使用されます。
(参照: 判別共用体 -Microsoft)
判別共用体の使用例
説明だけでは、分かりづらいと思うのでコードで実例を見ていきたいと思います。
以下の例は、PDF / TEXT / CSVのパターンを持つFileTypeという判別共用体を定義しています。このような判別共用体を用いることでPDF / TEXT / CSVの3つのファイル種別をFileTypeという一つの型で表すことができます。
type FileType = | PDF of data: byte list | TEXT of data: string | CSV of data: string let pdf = [0, 0, ...] let pdfFile: FileType = PDF pdf // 判別共用体.パターン という書き方も可能 let text = "This file ..." let textFile: FileType = FileType.TEXT text
また、F#では存在しない状態をnull
ではなくOption型で表現できます。このOption型は以下のような判別共用体で書かれています。
'a
はF#でのジェネリクス型です。
type Option<'a> = | Some of 'a | None let someValue = Some 1 // intの1をOption型でラップ let noValue = None // 値が存在していないことをOption型で表現
ツリー構造のような再帰的データ構造も判別共用体を用いて表現することができます。
type Tree<'a> = | Empty | Leaf of 'a | Node of 'a * Tree<'a> * Tree<'a> let intTree = Node(1, Node(2, Leaf(3), Leaf(4)), Node(5, Leaf(6), Empty))
判別共用体を使用するメリット
では、この判別共用体を使用するとなにが嬉しいのかを説明していきます。
F#では判別共用体を扱うときに、match式を使ってパターンマッチを行うことが多いです。
判別共用体自体の表現力にもメリットはあるのですが、判別共用体をパターンマッチと組み合わせることで、よりパワフルになります。
例えば、FileType型の変数に対してパターンマッチを行うときに、以下のようにTEXTとCSVのパターンは記述しているとします。このとき、例のようにPDFのパターンを記述していなければ、コンパイラが「Incomplete pattern matches on this expression...」という警告を表示してくれます。これにより、漏れているパターンがあれば即座に気づくことができます。
逆に、FileTypeで定義されたパターン以外の処理を書いた場合も、コンパイラが警告を表示してくれるため、安全に開発をすることができます。
また、パターン毎の処理を宣言的に書くことができるため、可読性という意味でも優れていると思います。
let textFile = TEXT "This is a text file." // PDFのパターンが漏れているmatch式 match textFile with | TEXT data -> printfn "%s" data | CSV data -> printfn "%s" data -> warning FS0025: Incomplete pattern matches on this expression... // パターンを満たしているmatch式 match textFile with | PDF data -> printDecodedBinaryData data | TEXT data -> printfn "%s" data | CSV data -> printfn "%s" data
まとめ
今回はF#の判別共用体について簡単に紹介させていただきました。
今後F#を使って開発する際は参考にしていただけると嬉しく思います。
F#では今回紹介していない便利な機能がまだまだありますので、皆さんもぜひF#さわってみてください。