-
Notifications
You must be signed in to change notification settings - Fork 309
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
[Proposal] Intersection types? #2655
Comments
Note that I'm willing to author a PR for this if we are going to add this 🙂 |
This is an interesting proposal. Right now there's no particular machinery in Fable for erased union types, as the F# compiler already checks the types when using the The drawback would be adding more tricks to Fable that won't work in standard F#. Also we're trying to make FSharp2Fable language agnostic in Fable 4 but this would likely add some JS-specific code (as with the Thoughts? @Booksbaum @MangelMaxime @Zaid-Ajaj @ncave @dbrattli |
The field checking you described is nice to have, but I think it's not a requirement. If I understand correctly, Unions and intersections are dual, so this would mean: |
If Intersection types can helps improve bindings generation I am interesting in having the feature available. For information: As discuss with @cannorin in private, improving TypeScript to Fable will be my next big project once I am done with API doc generation from Nacara. |
I have the feeling that if we add this feature we should have some field checking when constructing intersection types. If we don't do it, it won't be much of an improvement over just using It's not as advanced as in Typescript, but erased unions do guarantee safe destructing to some extent by compiling pattern matching as type testing (as we saw here). This works: type Foo() =
member _.Foo = "foo"
let test (x: U3<string, float, Foo>) =
match x with
| U3.Case1 x -> printfn $"This is a string: %s{x}"
| U3.Case2 x -> printfn $"This is a number: %f{x}"
| U3.Case3 x -> printfn $"This is a class: %s{x.Foo}"
// Compiled as:
// export function test(x) {
// if ((typeof x) === "number") {
// const x_2 = x;
// toConsole(interpolate("This is a number: %f%P()", [x_2]));
// }
// else if (x instanceof Foo) {
// const x_3 = x;
// toConsole(`This is a class: ${Foo__get_Foo(x_3)}`);
// }
// else {
// const x_1 = x;
// toConsole(`This is a string: ${x_1}`);
// }
// }
About the operator, I agree we should use |
BTW, I forgot to mention that we already have a similar field checking when casting an anonymous record into an interface (see the note about the Fable/src/Fable.Transforms/FSharp2Fable.Util.fs Lines 1051 to 1063 in b627427
|
I managed to modify open Fable.Core
open System.ComponentModel
type [<Erase; RequireQualifiedAccess>] I2<'a, 'b> = Box of obj with
[<Emit("$0")>] static member op_ErasedCast2(_: I2<'a, 'b>, _:'a) = jsNative : 'a
[<Emit("$0")>] static member op_ErasedCast2(_: I2<'a, 'b>, _:'b) = jsNative : 'b
type [<Erase; EditorBrowsable(EditorBrowsableState.Never)>] ErasedCast =
static member inline op_ErasedCast2(x: 'a, _: ^U) : ^U = Fable.Core.JsInterop.(!^) x
static member inline op_ErasedCast2(_: ErasedCast, _target) = _target // dummy overload
static member inline Invoke (x: ^T1) : ^T2 =
let inline call (_: ^i) (x: ^x) =
((^i or ^x): (static member op_ErasedCast2: ^x * ^T2 -> ^T2) x,Unchecked.defaultof<_>)
call Unchecked.defaultof<ErasedCast> x
let inline (!^) (x: 'T1) : 'T2 = ErasedCast.Invoke x
let x : U2<int, string> = !^42
let i : I2<int, float> = I2.Box 42
let y : int = !^i
let z : U2<int, string> = !^ !^i
Also, I think |
Fable has predefined types
U2
,U3
, ... so that tools such asts2fable
can use it to bind to union types coming from TypeScript.But Fable lacks the intersection counterpart, so
ts2fable
has no option but emittingobj
for intersection types, which is not very useful. How about addingI2
,I3
, ... types to fill the gap?The basic concept is described in the example below (online repl):
If we are to add these types to
Fable.Core
, I think it's better to extend the existing!^
operator to support intersection types rather than making a new operator, but thenop_ErasedCast
now has to include the return type as a dummy argument, which would be a breaking change. What do you think?The text was updated successfully, but these errors were encountered: