如何在其案例是其他案例的子集的情况下创建OCaml / F#DU类型?
例如,我想创建一个包含不同符号声明类型的符号表,例如程序类型,变量和函数。
乍一看,我可以看到变量包含其类型,函数还包含一个类型和许多参数变量。所以我想使用1 DU,而不是分成许多记录或别名:
type Symbol =
| TypeSymbol of id:string
| VariableSymbol of id:string * type':TypeSymbol
| FunctionSymbol of id:string * params':VariableSymbol list * type':TypeSymbol
但这是错误的,因为DU情况不能是其他情况的子集。如何重新格式化类型以声明DU情况是否相互递归?
答案 0 :(得分:6)
我从未使用过它们,但我认为您想使用GADT(这个答案适用于OCaml ):
type ts
type vs
type 'a symbol =
| TypeSymbol : {id : string} -> ts symbol
| VariableSymbol : {id : string; ty : ts symbol} -> vs symbol
| FunctionSymbol : {id : string; params : vs symbol; ty : ts symbol} -> 'a symbol;;
(*don't use "type" as a field name since it's a keyword of OCaml*)
正如您所看到的,这允许我完全指定构建构造函数的构造函数。
现在,当我想使用它们时:
# let t = TypeSymbol {id = "a"};;
val t : ts symbol = TypeSymbol {id = "a"}
# let v = VariableSymbol {id = "b"; ty = t};;
val v : vs symbol = VariableSymbol {id = "b"; ty = TypeSymbol {id = "a"}}
# let ve = VariableSymbol {id = "c"; ty = v};;
Characters 41-42:
let ve = VariableSymbol {id = "c"; ty = v};;
^
Error: This expression has type vs symbol
but an expression was expected of type ts symbol
Type vs is not compatible with type ts
正如您所看到的,如果我尝试使用VariableSymbol
之外的其他内容构建它,OCaml将不允许我使用构造函数TypeSymbol
创建符号。
有关手册,请参阅here,并祝您好运。
答案 1 :(得分:6)
对于F#,最简单的解决方案是创建更小的单例DU并引用它们:
type T = T of id:string
type V = V of id:string * type':T
type Symbol =
| TypeSymbol of T
| VariableSymbol of V
| FunctionSymbol of id:string * params': V list * type':T
通过分解它的用法可能是这样的。
let rec print x =
match x with
| TypeSymbol (T typeId) ->
typeId
| VariableSymbol (V (varId, T typeId)) ->
sprintf "%s : %s" varId typeId
| FunctionSymbol (funId, params', T typeId) ->
let printedParams =
params'
|> List.map (VariableSymbol >> print)
|> String.concat ") -> ("
sprintf "%s = (%s) -> %s" funId printedParams typeId