fsharp / fsharp/fslang-suggestions
Erased type-tagged anonymous union types
- Dominant language
- No language data
- Stars
- 373
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
## Modified Proposal (by @dsyme)
This is a suggestion to add adhoc structural type-tagged union types where
* type syntax `(A|B)` or `Typed(A|B)` or `Typed`
* each type of (A|B|C|...) is distinct and non-overlapping w.r.t. runtime type tests
* such types are erased to `object`
* introducing such a union value would need to be either explicit (e.g. using some new operator like `Typed`) or type-directed or both
See https://github.com/fsharp/fslang-suggestions/issues/538 for original proposal. e.g. this would allow some or all of these:
let generateValue1 () : Typed(int | string)) = if Monday then 2 else "4"
let generateValue2 () = if Monday then Typed 2 else Typed "4"
let eliminateValue (x : Typed(int | string)) = ...
match x with
| :? int as i -> ...
| :? string as s -> ...
let eliminateValue2 x = ...
match x with
| Typed(i : int) -> ...
| Typed(s; string) -> ...
type Allowed = Typed(int | string)
There are plenty of questions about such a design (e.g. can you eliminate "some but not all" of the cases in a match? Is column-polymorphism supported?). However putting those aside, such a construct already has utility in the context of Fable, since it corresponds pretty closely to Typescript unions and how JS treats values. It also has lots of applications in F# programming, especially if the use of `Typed` can be inferred in many cases.
Now, this construct automatically gives a structural union message type , e.g.
type MsgA = MsgA of int * int
let update1 () = Typed (MsgA (3,4))
type MsgB = MsgB of int * int
let update2 () = Typed (MsgB (3,4))
val update1 : unit -> Typed MsgA // actually : unit -> Typed (MsgA | ..), i.e. column-generic on use
val update2 : unit -> Typed MsgB // actually : unit -> Typed (MsgB | ..), i.e. column-generic on use
and a combination of update1 and update2 would give
let update = combine update1 update2
val update : unit -> Typed (MsgA | MsgB)
As noted in the comments, some notion of column-generics would likely be needed, at least introduced implicitly at use-sites.
## Original Proposal (@alfonsogarciacaro)
I propose we add erased union types as an F# first citizen. The erased union types already exist in Fable to emulate Typescript (non-labeled) union types:
http://fable.io/docs/interacting.html#Erase-attribute
Note that Fable allows you to define your custom erased union types, but this is because it's painful to type a generic one like `U2.Case1`. If the compiler omits the need to prefix the argument, this wouldn't be necessary and using a generic type can be the easiest solution.
The F# compiler could convert the following code:
```fsharp
// The name ErasedUnion is tentative
// The compiler should check the generic args are different
let foo(arg: ErasedUnion) =
match arg with
| ErasedUnion.Case1 s -> s.Length
| ErasedUnion.Case2 i -> i
// No need to instantiate ErasedUnion, but the compiler checks the type
foo "hola"
foo 5
// This doesn't compile
foo 5.
```
Into something like:
```fsharp
let foo(arg: obj) =
match arg with
| :? string as s -> s.Length
| :? int as i -> i
| _ -> invalidArg "arg" "Unexpected type"
```
- **Pros**: It will make the Fable bindings generated from Typescript declaration files much more pleasant to work with.
- **Cons**: It's a feature that seems to be exclusively dedicated to interact with a dynamic language like JS.
- **Estimated cost** (XS, S, M, L, XL, XXL): S
## Alternatives
For Fable it's been suggested to generate overloads in the type bindings instead of using erased union types:
```typescript
interface IFoo {
foo(arg: string | number): void;
}
```
```fsharp
type IFoo =
abstract foo: arg: string -> unit
abstract foo: arg: number -> unit
```
However these has some problems:
- It can quickly explode when you have several erased union arguments
- Due to type inference the F# compiler many times doesn't know which overload to use
- Cannot be used in properties
- Doesn't let you use erased unions yourself.
## Affadavit
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
* [x] I would be willing to help implement and/or test this
* [ ] I or my company would be willing to help crowdfund F# Software Foundation members to work on this
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.