--- breaks: false tags: public-tech --- # 定義せずにバリアントを使う(多相バリアント) この記事は [OCaml Tips Advent Calendar 2022](https://adventar.org/calendars/8396) の8日目です。 OCaml の特徴的な機能の一つにバリアント(variant, 代数型データ構造とも)があります。 C 言語のバックグラウンドがある人向けには `enum` の拡張と思うと話が早い、 こういうやつです。 ```ocaml= (* 定義 *) type foo = | A (* 引数なし *) | B of int (* int を受け取る *) | C of (string * string) (* 使う *) let f (a : foo) = match a with | A -> "A" | B n -> string_of_int n | C (s1, s2) -> s1 ^ s2 ;; f (B 30) ``` バリアントを使うときには、まず定義をして、どんなコンストラクタ(`A` とか `B` とか)が あるかを明示的に書く必要があります。しかし、一つの関数の中だけで補助的にバリアントを 使いたい場合などに定義を書くのは億劫です。 また、複数の関数でよく似たバリアント(特にコンストラクタが一つだけ引数が異なるなど) を使いたい場合に、各々別々のバリアントを定義してコンストラクタの名前をかぶらないように 調整するのは面倒です。 このような場合には多相バリアントが有用です。多相バリアントは、ざっくりいうと 定義しなくても使えるバリアントです[^defined-poly-variant]。例えば、先程の例に出てきた関数 `f` は 多相バリアントを使うと次のようにかけます。 [^defined-poly-variant]: 定義しても使えます。実のところ定義が必要・不要はあんまり重要なことではないはずなんですが、分かりやすさ重視でこう書いています。詳細は[ドキュメント](https://v2.ocaml.org/manual/polyvariant.html)を参照してください。 ```ocaml= (* 定義不要 *) (* 使う *) let f a = match a with (* コンストラクタに ` をつける *) | `A -> "A" | `B n -> string_of_int n | `C (s1, s2) -> s1 ^ s2 ``` このようにして定義した関数 `f` は次のような型を持ちます。 ``` val f : [< `A | `B of int | `C of string * string ] -> string = <fun> ``` `[< ... ]` の部分は `f` に渡すことができる値がどんなものかを表しています。 今回の `f` の定義では、`f` 内部で使っているコンストラクタで構成された 値のみを受け取り、それ以外の値を渡そうとすると型エラーになります。 ``` # f (`B 30) (* これは OK *) # f (`D 30) (* `D は使っていないのでだめ *) Error: This expression has type [> `D of int ] but an expression was expected of type [< `A | `B of int | `C of string * string ] The second variant type does not allow tag(s) `D ``` 内部で使っていないものでも受け取れるようにするためには、ワイルドカードのパターンを 追加する必要があります。 ```ocaml= let f a = match a with | `A -> "A" | `B n -> string_of_int n | `C (s1, s2) -> s1 ^ s2 | _ -> "no" (* `A, `B, `C 以外はここで回収される *) ;; f (`D 30) (* 今度は通る *) ``` 関数 `f` の型は次のようになります。不等号の向きが逆になっていることに注目してください。 ``` val f : [> `A | `B of int | `C of string * string ] -> string = <fun> ``` ## 参考 - https://v2.ocaml.org/manual/polyvariant.html