[RFC FS-1092 Discussion] - Anonymous type-tagged unions discussion #519
Replies: 20 comments 62 replies
-
Benefits of the feature threadSomething that is not elaborated on much is the benefit of erased/ad-hoc unions over discriminated unions. Using anonymous records as an example, here are some benefits over named records
Is there a list like this can be drafted for Erased Union types? |
Beta Was this translation helpful? Give feedback.
-
Interop threadThere are some questions raised by some folks about interop. How will C# "see" erased unions? How does this differ, if at all, from how it "sees" Discriminated Unions? |
Beta Was this translation helpful? Give feedback.
-
Component design threadWhat kind of component design considerations do we envision with this feature? One of the things listed in the RFC is being able to effectively get function overloading. That's obviously great when you public surface area is F# functions. But this raised some questions. Consider the following: type C() =
static member M(x: (int | string)): (double | int) = ...
static member M(x: (double| string)): (double | int) = ...
static member M(x: (int | string)): (int | double) = ...
static member M(x: (int | string)): (double | string) = ... From an orthogonality standpoint, code like this seems like it should be valid. What kind of components and patterns would benefit from this kind of code? What kind of guidance should generally be issued? |
Beta Was this translation helpful? Give feedback.
-
Supporting (string) literalsThe original lang suggestion was motivated for Typescript interaction with Fable. In Typescript, it's very common that erased unions accept literals, mainly strings. The feature would be much more helpful for Fable (and I assume also for other F# to JS/Python/... transpiling tools) if F# also supported this. It should be helpful for standard F# as well. let doSomething (mode: ("auto" | "default" | (* flags *) {| a: int; b: bool |})) = ()
// Named literals could work too as in pattern matching but maybe they're confusing?
let [<Literal>] Auto = "auto"
let [<Literal>] Default= "default"
let doSomething2 (mode: (Auto | Default)) = () This can also be a much better replacement for type RequestMode = ("same-origin | "no-cors" | "cors") |
Beta Was this translation helpful? Give feedback.
-
Syntax thread
Since this is erased to an object, I suppose we can add a keyword to signal erasing, like as suggested in fsharp/fslang-suggestions#712. So we will have |
Beta Was this translation helpful? Give feedback.
-
Will erased unions impact the possibility of getting actual anonymous unions or polymorphic variants ? |
Beta Was this translation helpful? Give feedback.
-
When reading the RFC part of it seems like its surfacing something like the internal only https://github.com/dotnet/fsharp/blob/main/src/fsharp/FSharp.Core/prim-types.fs#L1172-L1187 Why cant that internal type test be made public as this would allow generics to be treated efficiently in a pattern match, currently its for internal use but would be just as useful as this aspect in erased unions and the code is already there. |
Beta Was this translation helpful? Give feedback.
-
If I understand this feature correctly and if it gets implemented, I think it should be called Anonymous Union Types instead of Erased Union Types, because it appears to work like declaring an unnamed union inline which is exactly what anonymous records do. Edit: I guess @JaggerJo beat me to the punch |
Beta Was this translation helpful? Give feedback.
-
With inherited types is the order of types in a match important? Say you have 4 different subtypes inheriting and want to pattern match: type A() = class end
type B() = inherit A()
type C() = inherit B()
type D() = inherit C()
let test (x:A|B|C|D) =
match test with
| :? A as a -> printfn "%A" a
| :? B as b -> printfn "%A" b
| :? C as c -> printfn "%A" c
| :? D as d -> printfn "%A" d
test (D()) Looking at this code if it were a common occurrence to pattern match I could see that creating some syntactic sugar to optimise a little would be better, so they can be dealt with as normal DU's unwrapping:
|
Beta Was this translation helpful? Give feedback.
-
I'm about to read through the discussion, but just to mention I've taken a pass over the RFC and it is now here: https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1092-anonymous-type-tagged-unions.md The revised RFC uses different motivating examples and includes very specific and extensive guidance on when it is reasonable to use these types. The RFC now also uses the name "anonymous type-tagged unions" - it's a bit clumsy but specific |
Beta Was this translation helpful? Give feedback.
-
How does interop between F# Projects look like? |
Beta Was this translation helpful? Give feedback.
-
I feel that type information for RHS is known, what we don't know is the "must-convert-to" until we involve That said, I understand the example given. |
Beta Was this translation helpful? Give feedback.
-
Pattern MatchingOne question I have, on which myself am unsure about is how should pattern work? Should it be a type test or instead something else. Using type-test the following case would have to be specially handled. let handleSize (size: (int|int8|double|string)) =
match size with
| :? (int|int8) -> printfn "Got Integer"
| :? (double|string) -> printfn "Got Double or Float)" The current draft changes as it stands now, for There are few options I can think of.
let handlesize2 (size: (int|int8|double|string)) =
match size with
| :? int
| :? int8 -> printfn "got integer"
| :? double
| :? string -> printfn "got double or string"
let handlesize3 (size: (int|int8|double|string)) =
match size with
| (int|int8) i -> printfn "got integer"
| double d -> printfn "got double"
| string s -> printfn "got string" |
Beta Was this translation helpful? Give feedback.
-
Working with ListsWould we be able to do something like this: let items:(int|string) list = [ 1; "test"; 3 ] This would be very useful as a way of allowing lightweight unions of lists without the need to create lots of custom discriminated unions; this gets more problematic if you want to have e.g. one list that accepts ints or strings and another that accepts ints or floats or strings, but if the above was allowed, this would be a succinct way of solving this. In Farmer I come up against this very frequently, especially within the context of computation expression keywords. One of the design goals of Farmer, from the consumer point of view, is to limit exposure to things like DUs, or at least keep them as simple as possible; the above feature would be a great way to maintain type safety whilst keeping an API lightweight and generally "out of the way". |
Beta Was this translation helpful? Give feedback.
-
Accessing common membersWould we be able to write: let count (x:(System.Collections.IDictionary|System.Collections.Generic.IDictionary<_, _>)) = x.Count |
Beta Was this translation helpful? Give feedback.
-
I think this feature will help with a whole raft of issues that are either currently solved today with cumbersome Named DUs or by raising runtime errors and being careful to avoid them. I personally hit these issues when working on wrapping Deep Learning Libraries and when building a distributed actor system. On reading the proposal and discussion I thought I'd add my 2c. Specifically I have some concrete use cases in mind that may need some consideration.
let gemm(A:'a,B:'a when 'a : Matrix<2,'T>,alpha : 'T,beta : 'T when 'T : int | half | float | float32) : Matrix<2,'T> =
when 'T : int = GemmI(A,B,alpha,beta)
when 'T : half = GemmH(A,B,alpha,beta)
when 'T : float32 = GemmF(A,B,alpha,beta)
when 'T : float = GemmD(A,B,alpha,beta)
// Perhaps non-matched cases would be prevented from being entered at the call site;
// for example instead of warning that the match statement is not exhaustive perhaps the
// non-matched combinations could cause a type error at the call site.
let gemm(A:Matrix<2,'T>,B:Matrix<2,'T>,alpha:'T, beta:'T when 'T : int | half | float | float32) : Matrix<2,'T> =
match A,B,alpha,beta with
| :? int | :? int | :? int | :? int -> GemmI(A,B,alpha,beta)
| :? half | :? half | :? half | :? half -> GemmH(A,B,alpha,beta)
| :? float32 | :? float32 | :? float32 | :? float32 -> GemmF(A,B,alpha,beta)
| :? float | :? float | :? float | :? float -> GemmD(A,B,alpha,beta)
/// 'T, 'U and 'V would be restricted based on their usage in the later functions.
/// In this case 'U = 'V = (float32 | float) where as T = (half | float32 | float)
let squeezeAndExcite(X:Matrix<4,'T>, weights1:Matrix<3,'U>, weights2:Matrix<2,'V>) =
conv2D(X,weights1) * matmul(weights1,weights2)
type ThinningAlgorithms = | Rosenfelt = 1 | Stentiford = 2 | ZhangSuen = 3
/// This example only works on grayscale (1 channel) of uint8 and float32, and
/// while other functions support all the thinning algorithms this one only supports ZhangSuen and Steniford.
let thin(X: Img<1, uint8 | float32>, ?algo : ZhangSuen | Steniford) = ( . . . )
let foo(x:int | float,y:int | float) : (int | float) =
require(equals(typeof(x),typeof(y))) // Analyzer restriction
match x, y with
| :? int, ? int -> x + y
| :? float, ? float -> x + y
| _,_ -> failwith "Mixed types are unsupported" // The analyzer would prevent this from being executed. |
Beta Was this translation helpful? Give feedback.
-
Will this feature support returning values? Sorry if I missed it. For example: let doSomeFileWork xxx : Result<SomeWork, (FileNotFoundEx | DiskOutOfSpaceEx | InvalidAccessRightsEx )> = ... This way we could bring ourselves little bit closer to checked exceptions and make exceptions part of functions signature. With upcoming analyzer support throwing in such functions could be a warning candidate. |
Beta Was this translation helpful? Give feedback.
-
I think we should be able to eliminate parenthesis, when this is used directly in functions? Thoughts @dsyme ? let foo(x: (int | float), y: (int | float)) : (int | float) = vs let foo(x: int | float, y: int | float) : int | float = Less noise, imo |
Beta Was this translation helpful? Give feedback.
-
Performance question, is the plan for the compiled output to include the If the compiled output is going to include the type matching logic of the |
Beta Was this translation helpful? Give feedback.
-
i think this potentially would be great for also Fable world and TS translation? or at least for usage from TS users i think this would be a great plus as they already "know" this format for definying unions anonymously, + they would undertand this easilly in signtures too. but just a profane consideration |
Beta Was this translation helpful? Give feedback.
-
Discussion thread for https://github.com/fsharp/fslang-design/blob/master/RFCs/FS-1092-anonymous-type-tagged-unions.md
Beta Was this translation helpful? Give feedback.
All reactions