I am teaching myself F#--For Fun and Profit!--and, while I've made some strides, I have run into a stumbling block with usage of algebraic types. Below is a JSON type that I coded to serialize an arbitrary JSON structure to a string. I am open to subjective comments on it's design and efficiency, of course, but I am mainly focussed on line 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 )
Anyone familiar with JSON knows that a key/value pair of a JSON object should be keyed on a string, not just any JSON element/value. Is there a way to further restrict the first type parameter of the Map? These, of course, do not work:
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 >
Thanks.
Referencing another case identifier from within a discriminated union is not possible. From Discriminated Unions,
Syntax
[ attributes ]
type [accessibility-modifier] type-name =
| case-identifier1 [of [ fieldname1 : ] type1 [ * [ fieldname2 : ] type2 ...]
| case-identifier2 [of [fieldname3 : ] type3 [ * [ fieldname4 : ] type4 ...]
[ member-list ]
This means that each case identifier must be of
some type. A case identifier itself is not a type.
One way you could achieve the same functionality is by breaking the discriminated union into multiple discriminated unions:
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>
and then defining JSON
as:
type JSON = Map<JSONKey, JSONValue>
Then, serialize
would need to be changed to let rec serialize ( element : JSONValue )
and
serialize( this )
would need to be changed to serialize( JObject this )
.
As @Ringil mentioned, Map<string, JSON>
will work in this situation, but this is not too extensible/restrictive.