fsharp / fsharp/fslang-suggestions

Merge fields of the same type on multi-case union structs

Open
#699 20 comments 31 reactions 0 assignees View on GitHub
approved-in-principle area: structs-byrefs-and-span
Dominant language
No language data
Stars
373
Forks
21
PR merge metrics
No merged PRs in 30d

Description

# Merge fields of the same type on multi-case union structs

I propose we change the way multi-case union structs are represented.

Let's have the following as an example:

``` fsharp
[]
type MyType =
| Case1 of v1:int
| Case2 of v2:int
| Case3 of v3:int
```

Internally, this type is represented as __three__ integers, plus another one to indicate the union case.

My proposal is to make unions like this (structs where all cases are of the same type (or tuple of types)), internally _store the value __only once___, like the following example.

The existing way of approaching this problem in F# is this one, but it ruins type-safety:

``` fsharp
type MyTypeTag =
| Case1 = 1
| Case2 = 2
| Case3 = 3

[]
type MyType = {
Tag: MyTypeTag
Value: int
}
```

## Pros and Cons

The advantage of making this adjustment to F# is generation of more compact types. Struct multi-case unions are mainly used for low-level code where space efficiency is an advantage.

The disadvantage of making this adjustment to F# is ~~lack of backwards-compatibility~~. Actually, not at all. We would still enforce the unique names requirement for the structs, just they would have to be redirected to the same field, for C# consumers, or return the default element when accessing the wrong field for the wrong union case.

## Some other thoughts

* For unions with 256 cases or less, we could compact the data structures even more by storing the tag field as a byte. This would apply to all DU types in general, ~~but I am not sure about the backwards compatibility~~, we could still _expose_ them as 4-byte signed integers in a property for C# consumers. There might also be issues mith memory alignment and other black magic like that.

* For even more compactness, we could _partially_ merge the same-type elements of the DU cases as in the example:

``` fsharp
[]
type MyType =
| Case1 of i1:int * c1: char
| Case2 of i2:int
| Case3 of s3: string * i3:int
```

The type above could be represented as a struct with only three fields instead of five: one `int` for the `i1`, `i2` and `i3` fields, one string for `s3`, and one `char` for `c1`. And their position inside the tuple wouldn't matter at all. But this would make the proposal significantly more complex. And it would also raise questions about nested tuples and other stuff like that.

## Extra information

Estimated cost (XS, S, M, L, XL, XXL): S

## Affidavit (please submit!)

Please tick this by placing a cross in the box:
* [x] This is not a question (e.g. like one you might ask on [stackoverflow](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
* [x] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.

Please tick all that apply:
* [x] This is not a breaking change to the F# language design
* [ ] I or my company would be willing to help implement and/or test this

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.