Skip to content
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

Union types #11

Closed
wants to merge 1 commit into from
Closed

Union types #11

wants to merge 1 commit into from

Conversation

markknol
Copy link
Member

@markknol markknol commented Oct 28, 2016

This proposes introduction of union types as a alternative to Either and EitherType. A union type describes a value that can be one of several types.

I think this is a valuable addition to the type system of Haxe, especially when dealing with JavaScript. What I propose is most likely missing details, but the idea should be clear. I leave these details up to the our skilled team members. Looking forward to your thoughts.

I'm inspired by the union types of TypeScript, which are described here https://www.typescriptlang.org/docs/handbook/advanced-types.html

» Rendered version here

@RealyUniqueName
Copy link
Member

RealyUniqueName commented Oct 28, 2016

haxe.extern.EitherType is currently in right place where it should be. It is only used for externs on dynamic targets. Using it (with any syntax) in Haxe (which aims to be strictly typed) will not only impact performance, but also introduce whole new type of runtime errors )

@markknol markknol changed the title Create 0000-uniontypes.md Union types Nov 3, 2016
@fullofcaffeine
Copy link

fullofcaffeine commented May 25, 2017

Is this ever going to be considered? What's the status?

@back2dos back2dos mentioned this pull request May 26, 2017
@Simn
Copy link
Member

Simn commented Jun 1, 2017

I'm willing to consider a A | B syntax which translated to EitherType, but that's about it...

@markknol
Copy link
Member Author

markknol commented Jun 1, 2017

Can you also consider A | B | C | D?

@nadako
Copy link
Member

nadako commented Jun 1, 2017

That should be the same as (((A | B) | C) | D)?

@ibilon
Copy link
Member

ibilon commented Jun 1, 2017

If that's done, can the error message be improved/customized?
Having something like

E should be haxe.extern.EitherType<haxe.extern.EitherType<haxe.extern.EitherType<A, B>, C>, D>

isn't very readable (https://try.haxe.org/#212cB) compared to E should be A | B | C | D

@nadako
Copy link
Member

nadako commented Jun 1, 2017

I don't think just EitherType needs any special syntax. I'd go with either "proper" type unions or nothing here.

Regarding TS-like type unions (with cool recursive union field types and stuff), I don't think it's a good idea to have those as language feature in Haxe, because it only works "nicely" in dynamic targets, while on static targets it would produce a lot of reflection field lookups in many cases (even if we make compiler smart about common base classes/interfaces).

For TypeScript they make sense because their goal is to provide typed way to deal with any JS craziness, but for Haxe, these are only needed to interface with dynamic extern APIs, such as JS, and EitherType provide the way to deal with it. So, again, I'm not convinced that this even deserves a special syntax.

PS I sometimes do import haxe.extern.EitherType as Or and then Or<Int,Or<String,Bool>> for brevity.

@nadako
Copy link
Member

nadako commented Jun 1, 2017

E should be haxe.extern.EitherType<haxe.extern.EitherType<haxe.extern.EitherType<A, B>, C>, D>

I once had an idea that it would be useful in some cases to have pluggable toString() for types that compiler would use for error messages and completion. It would be useful for EitherType as well as derived types defined with Context.defineType (e.g. for a @:genericBuild Const<T>). It could be implemented either as a macro function or some metadata containing an interpolated string (e.g. for EitherType @:typeDisplay("{T1} | {T2}") or something.).

@markknol
Copy link
Member Author

markknol commented Jun 1, 2017

..provide typed way to deal with any JS craziness, but for Haxe, these are only needed to interface with dynamic extern APIs, such as JS

Well I think I ask this because I mainly use Haxe for JS.

Q: If its only for externs and just wrapper around the EitherType does it still add something? Otherwise lets close this request. I understand it requires more dynamic kinds to deal with, which seem to be hazzle (for like optimizations etc). I think it would be inconsistent for Haxe to only have it in externs, no?

For me union types was to provide a more elegant looking solution to work with dynamic stuff and could provide somewhat more flexible constraints.

@jcward
Copy link

jcward commented Jun 21, 2017

Values that can be multiple different types is a very dynamic / JavaScripty idea. I agree with @RealyUniqueName - haxe.ds.Either is a perfect, type-safe enum for dealing with this situation in your Haxe code, and haxe.extern.EitherType is a nice gasket for dealing with native code that does handle multiple types.

I've dealt with haxe.ds.Either, and the syntax isn't entirely terse, but there's an abstract called OneOf (by @mrcdk and @fponticelli) that helps. It allows automagic assignment to and from the Either, and writing functions that take either type. Here's a quick example: https://try.haxe.org/#31052

Via the magic of the abstract, note that:

  • I can directly assign either type to my OneOf
  • I can pass my OneOf to a function that takes either type
  • I can write a function using OneOf that takes either type

IMO, OneOf is good, optional syntax sugar for Either. Anything else is wasteful bloat in the compiler and makes me remember more syntax while using Haxe 😞


Edit to add: OneOf isn't all roses. To actually use it, you have to either 1) use switch as shown, 2) assign it to a typed variable, 3) pass it to a typed function arg, or 4) use a type check tell the compiler what type is should be:

	//trace(val+4);     // Error: OneOf<Int,String> should be Int
        trace((val:Int)+4); // valid!

Another gotcha -- OneOf<Int,String> isn't compatbile with OneOf<String,Int>. But this is easily avoided.

@kevinresol
Copy link

kevinresol commented Jun 23, 2017

Another gotcha -- OneOf<Int,String> isn't compatbile with OneOf<String,Int>. But this is easily avoided.

This can be mitigated by a @:to cast as well:

@:to inline function swap():OneOf<B, A> return switch this {case Right(b): Left(b); case Left(a): Right(a);}

@fullofcaffeine
Copy link

I just read @jcward and @kevinresol comments. I agree with them. Should we close now or does anyone else think we should proceed here?

@Simn
Copy link
Member

Simn commented Sep 25, 2017

I'll go ahead and close this. Still open to the idea of A|B being a shortcut to haxe.ds.EitherType<A, B>, but I don't think we should encourage this kind of typing in general. It makes sense in dynamic languages, but not so much in properly typed ones.

@Simn Simn closed this Sep 25, 2017
@markknol markknol deleted the patch-1 branch September 21, 2018 15:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants