递归区分联合案例类型

时间:2017-06-21 04:37:31

标签: f# ocaml record abstract-syntax-tree discriminated-union

如何在其案例是其他案例的子集的情况下创建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情况是否相互递归?

2 个答案:

答案 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