I suggest we ...

Allow single case unions to be compiled as structs

Single case unions are often used to define domain types that just wrap another primitive type. See http://fsharpforfunandprofit.com/posts/designing-with-types-single-case-dus/ for examples.

It would be great if we could mark these single case unions "inline" for optimized code generation. An "inline" single case union would not be converted to a class with a single "Item" property like an ordinary single case union, but instead the wrapped "Item" of the single case union would be passed directly (as is).

This would be similar to units of measure that are checked by the F# compiler, but are no longer visible in the generated code.

This would avoid the overhead of instantiating a class with the sole purpose of wrapping another type.

37 votes
Vote
Sign in
(thinking…)
Sign in with: facebook google
Signed in as (Sign out)
You have left! (?) (thinking…)
exercitus vir shared this idea  ·   ·  Flag idea as inappropriate…  ·  Admin →

10 comments

Sign in
(thinking…)
Sign in with: facebook google
Signed in as (Sign out)
Submitting...
  • exercitus vir commented  ·   ·  Flag as inappropriate

    While I was initially asking for something different (inline vs. struct), single case unions compiled to structs solve the same problem. While structs are more type-safe than inlined when used from other languages, it would not be completely safe either when used from other languages that can call the default non-overridable parameterless constructor of structs, which wrap reference types. But it's great that you prevent calling that constructor from within F#!

    To be completely safe, the author of the single case union compiled to struct must remember to wrap value types only, because wrapping reference types that default to `null` would be dangerous. This feature would still make a great addition to F# vNext.

  • Will Smith commented  ·   ·  Flag as inappropriate

    Obviously, we like wrapping types, but we also do not like heap allocation. Making them structs I think is what we want.

    If you are worried about performance, allowing single case unions to be structs is very sufficient and close enough to the performance as if the type was erased. It might be slower, but not by much, where as reference types might be on an order of magnitude slower if you are creating a lot of them. You also have access to inline functions, so you can avoid the copying of structs if you need to.

  • exercitus vir commented  ·   ·  Flag as inappropriate

    I don't think that the new title is appropriate. I was not asking for single case unions to be compiled as structs, but "inlined". For example:

    type inline SingleCaseUnion = SingleCaseUnion of int

    let f (SingleCaseUnion item) = ...

    should not compile to "SingleCaseUnion.Item" when passing the single value of "SingleCaseUnion" to function "f", but instead inline the int value in place of the "SingleCaseUnion.Item" property, so that "f" would look like this:

    let f (item : int) = ...

  • Expandable commented  ·   ·  Flag as inappropriate

    @exercitus vir: These benchmarks do not take garbage collection into account, as far as I can see.

  • exercitus vir commented  ·   ·  Flag as inappropriate

    I also like Expandable's idea of generating a struct instead of a reference type. But I think structs would also break type safety because they are automatically initialized and the default of the struct could be passed into a function that expects a non-default single case union.

  • Fraser Waters commented  ·   ·  Flag as inappropriate

    Wrapping with a struct sounds sensible. It would be lighter weight than currently and keeps it type safe when used from C#/VB/etc.

  • Expandable commented  ·   ·  Flag as inappropriate

    But that would break type safety when the code is used from outside F#. True, that's also the case with units of measure and the inline annotation is purely optional.

    But what about generating a struct instead? That would also avoid the heap allocation, instances of the type would not consume any additional memory compared to raw instances of the wrapped type and the just in time compiler should be able to inline pretty much all function calls (of course, the Equals methods, etc. should be overridden and point to the equivalent methods of the wrapped type).

F# Language

Feedback and Knowledge Base