Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Treat single-case union as the value #26

Closed
drhumlen opened this issue Oct 30, 2019 · 5 comments
Closed

Treat single-case union as the value #26

drhumlen opened this issue Oct 30, 2019 · 5 comments
Labels
enhancement New feature or request released: v0.7

Comments

@drhumlen
Copy link

drhumlen commented Oct 30, 2019

type PersonId = PersonId of int
let person = {| Name = "Tarmil"; PersonId = PersonId 123 |}

I would expect person to be formatted as

{name: "Tarmil", personId: 123} in the example above, not as:
{name: "Tarmil", personId: {Item: 123}} which is the closest thing I've found using options:JsonUnionEncoding.BareFieldlessTags ||| JsonUnionEncoding.Untagged.

Is it possible to add a super simple formatter specifically for Single-Case DUs? They're a really common pattern in F#, and we're using them to wrap basically all our primitives to add semantic meaning to them.

E.g. Map<PersonId, ProfileInfo> makes a lot more sense than Map<string, ProfileInfo> in which case you have to remember that the string refers to a person id. Single-Case DUs makes everything very easy to comprehend 😄

Maybe there already is support for it?

@drhumlen
Copy link
Author

drhumlen commented Oct 30, 2019

I've seen its common to add a [<Struct>] annotation to Single-Case DU types. E.g.

[<Struct>]
type PersonId = PersonId of int

Maybe that can be the cue that it should format it as such, and not as a normal union? 🤔

@Tarmil
Copy link
Owner

Tarmil commented Oct 30, 2019

We were recently discussing this exact feature with @thinkbeforecoding! It isn't supported yet, but it would make a lot of sense.

@Tarmil Tarmil added the enhancement New feature or request label Oct 30, 2019
@bartelink
Copy link

bartelink commented Oct 30, 2019

I and others have recently taken to using FSharp.UMX for this sort of stuff (example, which has the benefit (if the underlying type is string) of just working from the perspective of reflection based serializers and FsCheck, with a good balance between good enforcement of constraints, ease of migration and and perf

@drhumlen
Copy link
Author

@bartelink : I tried out FSharp.UMX now. It looks very nice! I'm tempted to replace all our usage on single-cased DUs with this.

Is there any reason to be hesitant about doing this? Do you advice for or against it?

@bartelink
Copy link

Yes, Eirik and Alfonso have done us a great service; I've used it in quite a few contexts and find it hard to fault in general. Some things to point out:

  • both sides of a contract can interop without sharing an assembly
  • normal reflection won't be able to separate the id type from its underlying type - great for serialization and/or e.g. strong typing all sorts of other things like LINQ etc
  • if the underlying type is a Guid but your canonical rendering demands formatting it with Guid.ToString("N"), you're in peril and will probably forget to register your custom Guid Converter
  • doesnt play well with unquote (but that normally forces you to Do The Right Thing)
  • you can make a mess by using % directly all over the place - code can be bard to read and you can't readily search for transitions to/from the id-type (which is why having the module MyType might be better in general)
  • you're using a Type Alias in some cases, which is rarely a good thing
  • biggest 'hole' is that it's not addressable as a Type - i.e. if you want FsCheck to use a particular Arb for some kind of Id, you can't do that in a general way. Some hacks:
let (|Id|) (x : Guid) = x.ToString "N" |> FSharp.UMX.UMX.tag
let inline mkId () = Guid.NewGuid() |> (|Id|)
let (|Ids|) (xs : Guid[]) = xs |> Array.map (|Id|)
let (|ToList|) (xs : 'T[]) = Array.toList xs

let [<Property>] prop (Ids (ToList tickets), Ids items) = 
   ... (where tickets is a XyzId[] and items is an AbcId list)

The samples folder in Equinox has some some usages that you might want to peep around - I also have comments around this stuff (none of this is actually using STJ yet - that's a big TODO in jet/FsCodec#14, hence my lurking here!) https://github.com/jet/equinox/blob/master/samples/Store/Domain/Infrastructure.fs#L40

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request released: v0.7
Projects
None yet
Development

No branches or pull requests

3 participants