Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Union types #4938

Open
blois opened this Issue Sep 5, 2012 · 14 comments

Comments

Projects
None yet

blois commented Sep 5, 2012

Currently Dart does not support function overloads based on parameter type and this makes it verbose/awkward to clone/wrap Javascript APIs which accept multiple types.

For example, WebSocket.send() can accept a String, a Blob or an ArrayBuffer.

In Dart, this can either be exposed as:
WebSocket.sendString, sendBlob, sendArrayBuffer which is verbose
or:
WebSocket.send(Dynamic data), which is no longer a self-documenting API.

It would be great to have some variant of union types, along the lines of JSDoc:
WebSocket.send(String|Blob|ArrayBuffer data);

Contributor

gbracha commented Nov 6, 2012

Set owner to @gbracha.
Added this to the Later milestone.
Added Accepted label.

Member

DartBot commented Jul 9, 2013

This comment was originally written by jjo...@google.com


Another use-case:

In the thread "Too type happy in dart:io?", mythz complained about the verbosity of setting the Content-Type of an HTTP response:

response.headers.contentType
      = new ContentType("application", "json", charset: "utf-8");

Instead, it would be nice to say:

response.headers.contentType = "application/json; charset=utf-8"

However, Anders noted that it's nice to have Content-Type reified as an object, so that you can access properties like so:

response.headers.contentType.mimeType

In the context described above, type-unions would allow the contentType setter to accept either a ContentType object, or a String; in the latter case, the String would be immediately parsed into a ContentType.

Any chance of this getting another look from the standards committee? I've run into several cases recently where I really want union types.

Contributor

kasperl commented Jul 10, 2014

Removed this from the Later milestone.
Added Oldschool-Milestone-Later label.

Contributor

kasperl commented Aug 4, 2014

Removed Oldschool-Milestone-Later label.

Member

DartBot commented Oct 7, 2014

This comment was originally written by @stevenroose


Another important use case is auto-complete in IDE's.

If a library declares a method with a dynamic return type, IDE's won't show any autocomplete options. If the method can only return two types of objects, the types could be specified using the union operator and IDE's could show auto-complete values for both types.

Example:

class MyClass {
  ...
  
  Map|String toJSON([bool stringify = false]) {
    Map jsonMap = _generateMap();
    return stringify ? const JsonEncoder.convert(jsonMap) : jsonMap;
  }
}

Isn't this relevant for the non-nullable proposal? Ceylon does this really nice thing where null es of type Null, so a nullable String is declared as String? which I believe is short hand for the union String | Null.

I find it somewhat strange that this is not higher on the priority list of the dart team. This feature really seems like an essential to me, that should have been in dart 1.0.

@cgarciae was thinking the same thing myself, union types seem like more bang for the buck. Maybe file a bug against the nullable types DEP?

According to discussion in #20906 union types were (are?) implemented in the old Dart Editor behind a flag. As far as I understand it, they were being inferred by the editor and used for hints and (possibly) code completion. There was not a syntax to declare a value as having an union type, though, which is useful for documentation purposes and it is what this bug is about.

Contributor

srawlins commented Sep 9, 2016 edited by lrhn

I think a great example of a union type use case is JSON:

JSON = Null | bool | num | String | List<JSON> | Map<String, JSON>
Member

lrhn commented Sep 9, 2016

Ah, a recursive union type. No need to make things simple, eh? :)

I think that might just be too "wide" a union to actually be useful. To actually use the type, you have to check against almost every type anyway:

if (x == null) { 
  ...
} else if (x is bool) { 
  ... 
} else if (x is nun)  {
...
} else if (x is String) {
...
} else if (x is List) {  // This should be enough to deduce that x is List<JSON>?
...
} else {  // Here you don't have to write (x is Map), that can be inferred.
...
}

That does expose an advantage to having the union type. I think we should be smart enough to know when an imprecise type test still only allows one type of the union, so:

JSON x = ..;
 if (x is List) {  .. x is known to be List<JSON> here ... }

We can't do that with the current dynamic.

For smaller unions like A = B | C, a negative B test is as good as a positive C test. Classical example:

var result = f(value);  // Inferred type (S|Future<S>).
if (result is Future) {  
  // It's Future<S>
} else {
  // It's S.
}

Maybe we can even be cleverer:

var x = something();   // type (List<int>|Set<int>|Map<int,int>).
if (x is Iterable) {
   // x has type (List<int>|Set<int>|(Map<int,int>&Iterable<int>)), not just Iterable<int>.
   if (x.contains(42)) print("Huzzah!");  // valid, all three types expose Iterable.contains.
} else {
   // x has type Map<int,int> - the List and Set parts could not refute the is test.
   if (x.containsKey(42)) print("Yowzer!");
}

If we have union types, I want this program to be warning free! :)

It might not be as simple as it looks, though :(

I'm really doing Boolean algebra on types. A type test creates an intersection of types:

T1 x = ...l
if (x is T2) { 
  // we know x is (T1&T2).
}

If T2 is a subtype of T1 then the result is just T2. That's what we currently support.
If T1 is a subtype of T2, the test is a tautolgy and you don't get any smarter. the result is still T1.
If T1 and T2 are unrelated, then you just get an intersection type. That may be an empty type, and you can't generally use it for anything.
Now, if T1 is a union type S1|S2, doing intersection should give us (S1|S2)&T2 which should be equivalent to (S1&T1)|(S2&T1).

The problem is that a positive is test can't refute any type because it's generally possible to implement any two interfaces on the same object. The negative test can refute something - if you are a Map, List or Set, then knowing that you are not an Iterable definitely precludes being a List or Set.

The possibilities are endless :)

@munificent munificent changed the title from Support type unions to Union types Dec 17, 2016

Member

sethladd commented May 31, 2017

If we had union types, Flutter APIs that deal with text get much nicer.

Instead of this: title: new Text('Example title') I could do: title: 'Example title' because we could annotate title named parameter has taking either String or Text.

Member

sethladd commented Jun 2, 2017 edited

Another case that might be helped by Union Types.

Today:

new Padding(
  padding: new EdgeInsets.all(8.0),
  child: const Card(child: const Text('Hello World!')),
)

Could be:

new Padding(
  padding: 8.0,
  child: const Card(child: 'Hello World!'),
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment