serdedotnet/serde

F# support

Opened this issue · 4 comments

After playing around with the C# implementation, I was thinking about what an F# implementation might look like using Myriad:

C#

[GenerateSerialize]
public partial class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}
public partial class Person : Serde.ISerialize
{
    void Serde.ISerialize.Serialize(ISerializer serializer)
    {
        var type = serializer.SerializeType("Person", 2);
        type.SerializeField("Name", new StringWrap(Name));
        type.SerializeField("Age", new Int32Wrap(Age));
        type.End();
    }
}

F#

[<GenerateSerialize>]
type Person = 
    {
        Name: string
        Age : int
    }

Generated:

module Person

let toSerializable (person: Person) =
    { new Serde.ISerialize with
        member this.Serialize(serializer: ISerializer) = 
            let type = serializer.SerializeType("Person", 2)
            type.SerializeField("Name", StringWrap(person.Name))
            type.SerializeField("Age", Int32Wrap(person.Age))
            type.End() 
    }

Usage:

{ Name = "John"; Age = 100 }
|> Person.toSerializable
|> JsonSerializer.Serialize
|> printfn "%s"

It wouldn't be quite as user friendly as the C# generator version because:

  • User would be required to manually include generated output file in the .fsproj file
  • Possible .toml config file needed
  • Myriad NuGet package required

Or maybe:

Generated:

module Person

let serializeWith (serializeFn: Serde.ISerialize -> string) (person: Person) =
    { new Serde.ISerialize with
        member this.Serialize(serializer: ISerializer) = 
            let type = serializer.SerializeType("Person", 2)
            type.SerializeField("Name", StringWrap(person.Name))
            type.SerializeField("Age", Int32Wrap(person.Age))
            type.End() 
    }
    |> serializeFn

Usage:

{ Name = "John"; Age = 100 }
|> Person.serializeWith (JsonSerializer.Serialize)
|> printfn "%s"

as an interim, maybe look at Chiron's combinator-based model and have a Chiron 'backend' for Serde.ISerialize and Serde.IDeserialize instances?

This issue was kind of DoA, but now I'm curious.

  1. Wouldn't that still involve generating the Chiron FromJson and ToJson static methods?
    If so, why not just generate using the SerializeField method as above and avoid the extra dependency?

  2. Do you think there is enough interest in F# community (now that net8 is out) to warrant adding F# support for this library?

EDIT: It just occurred to me that you were suggesting Chiron as an already-existing alternative.
(Question 2 still applies though.)

agocke commented

FWIW I'm still thinking about this and exactly how to implement it. The main hold-up is that I'm still iterating a bit on the Serde API itself.

For example, right now ISerialize implicitly uses the this parameter as the value, meaning that wrappers also require implementing the IWrapper type to be composable. This original API made sense in the context of languages with type classes (like Rust) because you could add support as an extension and the receiver would implicitly flow into your extension. But it's kind of obnoxious in C#. I've been bitten a few times by forgetting to implement IWrapper and then being stuck when trying to add composition.

So I think I might make ISerialize generic and add the value to the Serialize call. This will require changing the source generator, but that's not a big deal right now. When I add more F# support then I'll have to change Myriad as well, so there's a lot more iteration work. I'm waiting until I'm pretty sure the API's stable fore making more work for myself.