如何将F#联合类型的使用限制为特定选项

时间:2018-11-12 17:46:16

标签: generics f# algebraic-data-types discriminated-union

我正在自学F#(为了娱乐和利润!),尽管取得了长足的进步,但我却遇到了使用代数类型的绊脚石。以下是我编码的JSON类型,用于将任意JSON结构序列化为字符串。我当然愿意就其设计和效率发表主观评论,但我主要关注第7行:

type JSON =
    | JString of string
    | JNumber of decimal
    | JBool   of bool
    | JNull
    | JArray  of JSON list
    | JObject of Map< JSON, JSON >
with
    member this.Serialize  =
        let rec serialize ( element : JSON ) =
            match element with
            | JString str ->
                "\"" + str + "\""
            | JNumber num ->
                num.ToString()
            | JBool   bln -> 
                bln.ToString().ToLower()
            | JNull       ->
                "null"
            | JArray  ary ->
                "[" + String.concat "," ( List.map serialize ary ) + "]"
            | JObject obj -> 
                "{" + (
                    Map.fold (
                        fun state key value ->
                            state + ( match state with "" -> "" | _ -> "," )
                                  + ( serialize key ) 
                                  + ":" 
                                  + ( serialize value ) ) "" obj ) + "}"
        serialize( this )

任何熟悉JSON的人都知道,应该将JSON对象的键/值对键入字符串,而不仅仅是任何JSON元素/值。有没有办法进一步限制Map的first type参数?这些当然不起作用:

type JSON =
    ... elided ...
    | JObject of Map< JSON.JString, JSON >

...

type JSON =
    ... elided ...
    | JObject of Map< JString, JSON >

...

type JSON =
    ... elided ...
    | JObject of Map< string, JSON >

谢谢。

1 个答案:

答案 0 :(得分:4)

不可能从已区分的联合中引用另一个案例标识符。从Discriminated Unions

  

语法

     

[属性]

     

类型[可访问性修饰符]类型名称=

     

case-identifier1 [of [fieldname1:] type1 [* [fieldname2:] type2 ...]

     

case-identifier2 [of [fieldname3:] type3 [* [fieldname4:] type4 ...]

     

[[成员列表]

这意味着每个案例标识符必须是of某种类型。案例标识符本身不是类型。

实现相同功能的一种方法是将有区别的联合分成多个有区别的联合:

type JSONKey =
| JString of string

type JSONValue =
| JString of string
| JNumber of decimal
| JBool of bool
| JNull
| JArray of JSONValue list
| JObject of Map<JSONKey, JSONValue>

,然后将JSON定义为:

type JSON = Map<JSONKey, JSONValue>

然后,将serialize更改为let rec serialize ( element : JSONValue )serialize( this )需要更改为serialize( JObject this )


@Ringil 所述,Map<string, JSON>在这种情况下可以使用,但这并不是太可扩展/有限制性。