Is F# aware of its discriminated unions' compiled forms?
Asked Answered
K

3

10

A discriminated union in F# is compiled to an abstract class and its options become nested concrete classes.

type DU = A | B

DU is abstract while DU.A and DU.B are concrete.

With ServiceStack, the serialization of types to JSON strings and back can be customized with functions. With respect to the DU type, here's how I could do it in C#.

using ServiceStack.Text;

JsConfig<DU.A>.SerializeFn = v => "A"; // Func<DU.A, String>
JsConfig<DU.B>.SerializeFn = v => "B"; // Func<DU.B, String>
JsConfig<DU>.DeserializeFn = s =>
    if s == "A" then DU.NewA() else DU.NewB(); // Func<String, DU>

Is F# aware of its discriminated unions' compiled forms? How would I get the type of DU.A in F# at compile time?

typeof<DU> // compiles
typeof<DU.A> // error FS0039: The type 'A' is not defined
typeof<A> // error FS0039: The type 'A' is not defined

I can easily enough register a function for deserialization in F#.

open System
open ServiceStack.Text

JsConfig<DU>.RawDeserializeFn <-
    Func<_, _>(fun s -> printfn "Hooked"; if s = "A" then A else B)

Is it possible to register serialize functions wholly in F# for the concrete types DU.A and DU.B?

Knout answered 24/7, 2013 at 11:7 Comment(0)
K
10

Whilst all the behaviour (the abstract classes etc.) is not just an implemenation detail, it is actually defined by the spec, these things are not accesible from F# - this is a quote from the spec

A compiled union type U has:

· One CLI static getter property U.C for each null union case C. This property gets a singleton object that represents each such case.

· One CLI nested type U.C for each non-null union case C. This type has instance properties Item1, Item2.... for each field of the union case, or a single instance property Item if there is only one field. However, a compiled union type that has only one case does not have a nested type. Instead, the union type itself plays the role of the case type.

· One CLI static method U.NewC for each non-null union case C. This method constructs an object for that case.

· One CLI instance property U.IsC for each case C. This property returns true or false for the case.

· One CLI instance property U.Tag for each case C. This property fetches or computes an integer tag corresponding to the case.

· If U has more than one case, it has one CLI nested type U.Tags. The U.Tags typecontains one integer literal for each case, in increasing order starting from zero.

· A compiled union type has the methods that are required to implement its auto-generated interfaces, in addition to any user-defined properties or methods.

These methods and properties may not be used directly from F#. However, these types have user-facing List.Empty, List.Cons, Option.None, and Option.Some properties and/or methods.

Importantly, "these methods and properties may not be used from F#".

Klan answered 24/7, 2013 at 11:51 Comment(5)
I am wondering if this is part of F# language Spec, does this make implementing F# in JVM impossible? Because CLI is part of language now.Miscarry
@WeiMa - I would assume that similar inheritance can be modelled on the JVM. There has been some discussion of getting the compiler to output to the JVM on the mailing list, but I don't think there are any serious plans.Klan
@WeiMa: Note that the F# language supports features like value types and tail calls that the JVM cannot even express.Moten
@Jon Harrop: So, looks like not possible to have JVM-backed F#?Miscarry
@WeiMa: Not a fully compatible one, no. You could have a mostly-compatible port to the JVM but there isn't much motivation when Mono exists.Moten
L
6

Daniel is correct, you can do this by registering serialization functions for the base type DU. Here is a fuller example

open System
open ServiceStack.Text

type DU = A | B

let serialize = function
    | A -> "A"
    | B -> "B"

let deserialize = function
    | "A" -> A
    | "B" -> B
    | _   -> failwith "Can't deserialize"

JsConfig<DU>.SerializeFn <- Func<_,_>(serialize)
JsConfig<DU>.DeSerializeFn <- Func<_,_>(deserialize)

let value = [| A; B |]
let text = JsonSerializer.SerializeToString(value)
let newValue = JsonSerializer.DeserializeFromString<DU[]>(text)

Result:

val value : DU [] = [|A; B|]
val text : string = "["A","B"]"
val newValue : DU [] = [|A; B|]
Larrabee answered 24/7, 2013 at 16:18 Comment(0)
P
5

The fact that a DU in F# is a single type is key to its usefulness. The F# approach would be to use pattern matching:

JsConfig<DU>.SerializeFn <- function
  | A -> "A"
  | B -> "B"

This should work because the union cases are not only nested types in C#, but subtypes as well. Of course if ServiceStack doesn't consider base type serializers then this won't work.

Pejsach answered 24/7, 2013 at 14:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.