レコードタイプを構築しているとしましょう:
type thing {
fruit: string;
}
fruit
しかし、可能な値を文字列の固定セットに制限したいと考えています。
これを OCaml でバリアントとしてモデル化するのは自然なことのようです。
type fruit = APPLE | BANANA | CHERRY
type thing {
fruit: fruit;
}
ここまでは大丈夫です。
しかし、[@@deriving yojson]
これらの型で使用すると、シリアル化された出力は次のようになります。
{ "fruit": ["APPLE"] }
デフォルトでは、Yojson はバリアントをタプルとしてシリアライズしたいと考えて[<name>, <args>...]
います...そのロジックはわかりますが、ここでは役に立ちません。
次のようにシリアライズしたい:
{ "fruit": "APPLE" }
いくつかの ppx 派生プラグインを使用して、このモジュールをビルドして、必要に応じてデ/シリアル化することができました。
module Fruit = struct
type t = APPLE | BANANA | CHERRY [@@deriving enum, variants]
let names =
let pairs i (name, _) = (name, (Option.get (of_enum i))) in
let valist = List.mapi pairs Variants.descriptions in
List.to_seq valist |> Hashtbl.of_seq
let to_yojson v = `String (Variants.to_name v)
let of_yojson = function
| `String s -> Hashtbl.find_opt names s
|> Option.to_result ~none:(Printf.sprintf "Invalid value: %s" s)
| yj -> Error (Printf.sprintf "Invalid value: %s" (Yojson.Safe.to_string yj))
end
これは問題なく動作します...しかし、同じ方法で扱いたい他の「文字列列挙型」バリアントがいくつかあります。毎回このコードをコピーして貼り付けたくありません。
私はこれまでに得ました:
module StrEnum (
V : sig
type t
val of_enum : int -> t option
module Variants : sig
val descriptions : (string * int) list
val to_name : t -> string
end
end
) = struct
type t = V.t
let names =
let pairs i (name, _) = (name, (Option.get (V.of_enum i))) in
let valist = List.mapi pairs V.Variants.descriptions in
List.to_seq valist |> Hashtbl.of_seq
let to_yojson v = `String (V.Variants.to_name v)
let of_yojson = function
| `String s -> Hashtbl.find_opt names s
|> Option.to_result ~none:(Printf.sprintf "Invalid StrEnum value: %s" s)
| yj -> Error (Printf.sprintf "Invalid StrEnum value: %s" (Yojson.Safe.to_string yj))
end
module Fruit = struct
type t = APPLE | BANANA | CHERRY [@@deriving enum, variants]
end
module FruitEnum = StrEnum (Fruit)
それは型チェックのようで、次のことができます。
utop # Yojson.Safe.to_string (FruitEnum.to_yojson Fruit.APPLE);;
- : string = "\"APPLE\""
utop # FruitEnum.of_yojson (Yojson.Safe.from_string "\"BANANA\"");;
- : (FruitEnum.t, string) result = Ok Fruit.BANANA
...しかし、私がしようとすると:
type thing {
fruit: FruitEnum.t;
}
[@@deriving yojson]
私は得るError: Unbound value FruitEnum.t
バリアントのモジュールから再エクスポートしているためのようですがtype t = V.t
、よくわかりません。(または、yojson ppx がファンクターの結果を正しく「見る」ことができないためですか?)
どうすればこれを修正できますか?
また、バリアント モジュールを個別に定義するのをスキップして、次のようにできるようにしたいと考えています。
module Fruit = StrEnum (struct
type t = APPLE | BANANA | CHERRY [@@deriving enum, variants]
end)
...しかし、これによりエラーが発生します:
Error: This functor has type
functor
(V : sig
type t
val of_enum : int -> t option
module Variants :
sig
val descriptions : (string * int) list
val to_name : t -> string
end
end)
->
sig
type t = V.t
val names : (string, t) Hashtbl.t
val to_yojson : t -> [> `String of string ]
val of_yojson : Yojson.Safe.t -> (t, string) result
end
The parameter cannot be eliminated in the result type.
Please bind the argument to a module identifier.
何が悪いのかわかりません。