Replacing the switch statement with a match expression for Dart 2.0?

4,004 views
Skip to first unread message

Rasmus Eneman

unread,
Oct 31, 2015, 10:14:00 AM10/31/15
to mi...@dartlang.org
Me and a friend started working on a DEP for pattern matching using a Rust like match expression however, as we were creating examples and translated them to actual Dart code we saw that the big benefit is as a replacement for the switch-statement. They can provide nice syntactic suger for multiple if else branches, but is that enough to introduce a new concept?
However switch statements are a pretty complex concepts with fall though, or in Darts case not which already breaks with other languages.
To re-add fall though Dart do have labels which is another concept for a very limited use case.

Therefore I would like to raise the discussion if the switch statement should be dropped for Dart 2.0 and instead be replaced with a Rust like match expression.

When compared to switch statements the match expression does have a nicer syntax which is more similar to the rest of the language (switch the only statements where blocks starts with a colon and ends by magic),
and is simpler because there no fall though or labels (that behaviour can easily be replaced with function calls anyway) and does not break semantics with other popular languages.

The places were it would be a bit nicer than if/else would only come as an additional bonus.

Eric Leese

unread,
Nov 2, 2015, 3:05:46 PM11/2/15
to Dart Misc
I personally agree with this; I would love to see the goto-label inspired syntax go away along with the confusing goto-inspired semantics (implicit fallthrough) that have already gone. But for my tastes, Rust has not gone far enough. Why make the syntax special at all? Why create a new way of denoting a block, but reserve it only for one of the least used control flow structures? Also, why change the semantics of operators like | in a case?

My preferred syntax would be this:

match (someExpression)
case 1 {
  // matches if someExpression == 1
} case 2, 3 {
  // matches if someExpression == 2 or someExpression == 3
} case 2 | 3 {
  // matches if someExpression == 2 | 3, that is if someExpression == 6
} case [x] {
  // matches if someExpression is a list with one element, and that element == x. (x must already be defined)
} case [var y] {
  // matches any list with one element, assigns the value of that element to y
} case [_, x, var y] {
  // matches a list with three elements if the middle is equal to x, assigns the last element to y
}

No special block syntax; it looks like an if. Potentially any expression can be used in a case statement, though not every expression can contain a variable declaration or an _.

Bob Nystrom

unread,
Nov 2, 2015, 3:31:46 PM11/2/15
to General Dart Discussion
On Sat, Oct 31, 2015 at 7:14 AM, Rasmus Eneman <pie.o...@gmail.com> wrote:
Me and a friend started working on a DEP for pattern matching using a Rust like match expression

Awesome! I have a similiar proposal from a very long time ago (back before Dart had been publicly released). I'm excited to see other people wanting it too.
 
however, as we were creating examples and translated them to actual Dart code we saw that the big benefit is as a replacement for the switch-statement. They can provide nice syntactic suger for multiple if else branches, but is that enough to introduce a new concept?

To me, I think the real value is that it combines a type test and a binding. In Dart today, it's pretty common to have code like:

if (foo.bar is SomeType) {
  var bar = foo.bar as SomeType;
  ...
}

That redundancy drives me crazy. But as more and more people use strong mode, users will find themselves hitting code like this more often.
 
However switch statements are a pretty complex concepts with fall though, or in Darts case not which already breaks with other languages.

Yes, the scoping for switch is weird. For a pattern-matching system that did scoped binding, you obviously cannot allow fallthrough.
 
Therefore I would like to raise the discussion if the switch statement should be dropped for Dart 2.0 and instead be replaced with a Rust like match expression.

Another option would be to just have both.

I'd personally be happy removing switch to make the language simpler, but there's a lot to be said for compatibility and familiarity.

Cheers!

– bob

Robert Åkerblom-Andersson

unread,
Nov 2, 2015, 4:56:33 PM11/2/15
to Dart Misc
Hi,

Speaking of the switch statement, have the team considered removing the need for an explicit need for "break" in switches. I think removing it could be done pretty easily (could even be implemented as a transformer inserting "break;" statements as a first version) without breaking any or much code.

Bob, as I understand it you have looked quite a bit on how Dart is used internally, is there any code you can think of that relies on the "fall through" behavior of the current switch design? I personally at least have never wrote a single program using a switch without using break... But maybe I am missing something useful with the fall through design? If so it would be interesting to know how people uses it...

Removing the explicit "break" from switch statements might be small enough that it does not break too much code, big enough that it's a feature that could be worth doing (and simple enough to implement that it does not take too much resources).

Example:
final x = 1;

switch(x) {
 
case 1: print("one");
 
case 2: print("two");
 
case 3: print("three");
 
default: print("anything");
}


Regards, Robert

tatumizer-v0.2

unread,
Nov 2, 2015, 7:52:58 PM11/2/15
to Dart Misc
Switch is used in  parsers where fall-through is common:
switch (c) {
  case CR:
  case LF:
  case SPACE:
     // handle whitespace
  ...
}
Dart doesn't have any other syntax for multiple cases.

Jan Mostert

unread,
Nov 3, 2015, 12:42:43 AM11/3/15
to Dart Misc
It'd be nice to have an optional paramater in a switch to prevent fallthrough:

switch (c, noFallThrough: true){
  case CR:
    // do something
case LF:
  continue; // instead of break, now use continue to trigger fallthrough 
case SPACE:
  // do something else
default:
  // throw exception
}

I quite like the match idea, would be nice to have similar option to fallthrough or not fall through using continue / break to trigger fallthrough / not falling through.

Would it hurt having both switch and match?
Or maybe allowing an optional param inside a switch that will allow a switch to operate like match ?

switch (c, noFallThrough: true, regex: true){
    case "[0-9]+":
       // do something
   case "[A-Z]+":
       // do something else 
   default:
      // meh
}



--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

Jan Mostert

unread,
Nov 3, 2015, 12:46:01 AM11/3/15
to Dart Misc
Or even cleaner, to use regex inside switch:

switch(match(c), noFallThrough: true){

}




Jan Mostert

unread,
Nov 3, 2015, 1:01:50 AM11/3/15
to Dart Misc
The above syntax will also allow more advance usages of switch:

switch(not match(c), noFallThrough: true){

or 

switch(!match(c), noFallThrough: true){

switch(is(c), noFallThrough: true){
  case String:
  case bool:
  case int:
  default:
}

or not is:

switch(!is(c), noFallThrough: true){





Jan Mostert

unread,
Nov 3, 2015, 1:14:32 AM11/3/15
to Dart Misc
How about taking it one step further and allowing matching all case lines at once using async and then executing the first matching block to complete in order from top to bottom:

switch(match(c), noFallThrough:true) async {
   case retreiveA():

  case retreiveB():

  case retreiveC():

  default:

}

Retreive A / B / C returns Future<String>, all are executed at the same time, once A is complete, try and match the result with the regular expression c, if it doesn't match, wait for B, then wait for C etc.

Without the async keyword, it would do them one-by-one from top-to-bottom. with async it executes all of them at the same time, but waits from top to bottom.

There's plenty of reason to keep the switch statement and just wrap the paramater that usually goes into switch with something to indicate a different type of switch.




Erik Ernst

unread,
Nov 3, 2015, 4:43:26 AM11/3/15
to Dart Misc
I think there is a tension between two different goals here: A simpleminded, low-level `switch` construct allows for compilation to code that runs fast (e.g., a single table lookup based on a set of simple values "sufficiently similar to the natural numbers up to N"), and it allows for non-structured control flow ("goto"). This is the C-ish version of `switch`.

A more general `switch` construct could be defined as syntactic sugar on top of other constructs, e.g., a chain of `if`s (surely we could invent a definition that  would allow for general expression evaluation for the value tested in each `case`, we could allow for `async`, etc.). A good example of a pretty general mechanism in this area is Scala pattern matching, for instance, `x match { case 1 => "one"; case y: Int => "not one"; case _ => "not a number" }`. This allows for invocation: the `{ case .. }` construct is a function literal that matches several different cases based on value comparison, type tests, or `unapply` based pattern matching, which is completely user-defined.

I tend to think that it is better to have the ability to express the general constructs in the language already available, rather than setting out to do some fine-grained tailoring on specific control structures.

So maybe the really nice thing to have would be a construct that works a bit like the Scala pattern matching function literals? Setting out from a `Map` literal (which already handles the cases where `==` is all we need), it just needs to support extra tests (like `is int`, or `(x) => x % 2 == 0`, or `unapply`). Those functions would then support a variety of generalized `switch` constructs (even though we may need to put the scrutinee after the list of cases unless we also add a bit of syntactic sugar for pipeline style function invocation, like "->" in BETA or "|>" in F#). In that case it might be a reasonable trade-off to keep the built-in `switch` simple, or to eliminate it from the language entirely.

--
Erik Ernst  -  Google Danmark ApS
Skt Petri Passage 5, 2 sal, 1165 København K, Denmark
CVR no. 28866984

Jan Mostert

unread,
Nov 3, 2015, 4:56:18 AM11/3/15
to Dart Misc
Wouldn't it be possible to decide on compile time whether that switch-statement is a simple low-level switch or a switch on steroids?
The type information should be enough to determine whether we're looking at a simple enum / integer type switch or a more complicated switch that needs to be converted to if-else statements. If no type information is available, falling back to if-else doesn't sound to bad - for better performing switch statements, you'd want the type information present.

Sounds much better re-using switch than introducing a new control structure to evaluate a new "type of switch".
Now if we want a switch-like structure for something other than regex (for which match was suggested), we'd need to invent a new control structure.

I'm not familiar with the way Scala does it, will do a bit of reading, sounds interesting.


Jan Mostert

unread,
Nov 3, 2015, 5:08:02 AM11/3/15
to Dart Misc
This Scala syntax does look useful for inlinining switches:

Scala:

val x = (i: @switch) match {
    case 1  => "One"
    case Two => "Two"             // replaced the '2'
    case _  => "Other"
} Dart equivalent could be
String c = ... String s = switch(match(c), noFallThrough:true){
case "[0-9]+": "Number";
case "[A-Za-z]+": "Word";
default: "Unknown";

} or // equals(c) or just c since equals is implied: String c = ...
String s = switch(equals(c), noFallThrough:true){
case "1": "One";
case "2": "Two";
...
default: "Unknown";
} or var c = ...
String s = switch(is(c), noFallThrough:true){
case int: "Integer";
case bool: "Boolean";
...
default: "Unknown";
}


In this scenario we added an int type, so the switch can act like a "normal low-level" switch.
int c = ...
String s = switch(c, noFallThrough:true){
case 1: "One";
case 2: "Two";
...
default: "Unknown";
}




Joel Trottier-Hébert

unread,
Nov 3, 2015, 7:31:45 AM11/3/15
to mi...@dartlang.org
I don't give my output often on these DEP proposals. But my vote would go to a F# like (or Ocaml, Scala, whatever) pattern matching mechanism. It is just so powerful, gives possibility to have very readable code.

Yet I don't know if the current way things are implemented in Dart would allow doing this.

Thanks!

kc

unread,
Nov 3, 2015, 10:35:59 AM11/3/15
to Dart Misc
On Tuesday, November 3, 2015 at 12:31:45 PM UTC, Joel Trottier-Hébert wrote:
I don't give my output often on these DEP proposals. But my vote would go to a F# like (or Ocaml, Scala, whatever) pattern matching mechanism. It is just so powerful, gives possibility to have very readable code.

Yet I don't know if the current way things are implemented in Dart would allow doing this.

A question was raised re pattern matching at the Dart Summit:


K.

Joel Trottier-Hébert

unread,
Nov 3, 2015, 10:48:44 AM11/3/15
to mi...@dartlang.org
Yeah I've seen that. I've also opened an issue for the SDK more than a year ago, and they closed it so ... :P

--

Bob Nystrom

unread,
Nov 3, 2015, 1:54:27 PM11/3/15
to General Dart Discussion
On Mon, Nov 2, 2015 at 9:42 PM, Jan Mostert <jan.m...@gmail.com> wrote:
switch (c, noFallThrough: true){
  case CR:
    // do something
case LF:
  continue; // instead of break, now use continue to trigger fallthrough 
case SPACE:
  // do something else
default:
  // throw exception
}

I quite like the match idea, would be nice to have similar option to fallthrough or not fall through using continue / break to trigger fallthrough / not falling through.

Aside from the general weird of having a "named parameter" as part of a statement syntax, consider:

var string = "continue";
var flag = true;
while (true) {
  switch (string, noFallThrough: flag) {
    case "turn off fallthrough":
      flag = false;
      string = "continue";
      break;

    case "continue":
      continue;

    default:
      print("fell through");
  }

  print("didn't continue");
}

You really really don't want the semantics of a reserved word changing based on a value at runtime.
 

Would it hurt having both switch and match?

No, I think it's feasible to have two separate statements.

Cheers!

– bob

Rasmus Eneman

unread,
Nov 3, 2015, 2:53:48 PM11/3/15
to Dart Misc
I maybe should have created two different threads, one for pattern matching and another one for replacing the switch statement :)
While I would like it removed as said, introducing pattern matching as well would be great by itself.

One thing we are debating is every kind of pattern should be specified on a syntactical level or if it should handle expressions producing objects which can implement a method to handle matching.

The expression route is pretty interesting, say if we added a hasMatch(other) method to Object that the match expression calls to check if the current pattern matches, implemented as other == this. Then we have support for matching simple values. RegExp already overrides hasMatch which means that we also have support for matching against regexes. It can be expanded by libraries with ranges and other stuff. To support type tests or comparisons bool could override hasMatch as other is bool ? other == this : other and we could have patterns like x is num or x < 10.
What's tricky in this route is declaration, as we use the scope variables can't just be declared to the value we are matching. This could be solved by variables being declared with var but that start to become pretty different from other languages with pattern matching. Another problem is destructuring of lists, if we use brackets justs like list literals (and like array destructuring in ES2015) we would have a hard time distinguishing them from list literals.

The other route would be much closer to languages like Scala and Rust and we wouldn't need to invent a lot of solutions, just specify what kind of patterns we want to allow.
The values checks would probably be covered with nullLiteral, numericLiteral, booleanLiteral, stringLiteral, symbolLiteral
And then we probably want typeTest and identifier (for a catch all state, _ is a normal parameter in rest of Dart so why stop now...).

List destructuring is pretty important, to handle matching on multiple values on the same time.
Object destructuring would be cool, and maybe comparison operators and ranges?

Some way to define alternative patterns is also needed. I like the pipe because it's used in other patterns like regex but as said it can be confused with a boolean or...
--
Rasmus Eneman

kc

unread,
Nov 11, 2015, 10:09:22 AM11/11/15
to Dart Misc
On Tuesday, November 3, 2015 at 3:48:44 PM UTC, Joel Trottier-Hébert wrote:
Yeah I've seen that. I've also opened an issue for the SDK more than a year ago, and they closed it so ... :P

Lars Bak is half-right and half-wrong. 

Right - simple semantics are good. No magic or weird deopts/insecurity.
Wrong - expressiveness (without boilderplate) is important. A switch with weird/tricky limitations from 1972 seems wrong when compared with what other  (lower level!) langs are offering.

K.

Paul Brauner

unread,
Nov 11, 2015, 1:20:54 PM11/11/15
to Dart Misc
Whatever syntax/semantics we choose, I wish it was based on some view mechanism like in Scala and F# (or Haskell with extensions) which is allows to foreserving abstractions. That is, you don't have to know the concrete type of some object in order to match against it. Also it'd be nice to consider having match be the minimum syntactic sugar for making first-class patterns (patterns as values) user friendly, like in this newspeak paper.

Paul

--

Justin Fagnani

unread,
Nov 11, 2015, 2:39:12 PM11/11/15
to General Dart Discussion
On Wed, Nov 11, 2015 at 10:20 AM, 'Paul Brauner' via Dart Misc <mi...@dartlang.org> wrote:
Whatever syntax/semantics we choose, I wish it was based on some view mechanism like in Scala and F# (or Haskell with extensions) which is allows to foreserving abstractions. That is, you don't have to know the concrete type of some object in order to match against it.

Why would you have to know the concrete type? I would assume that pattern matching would work against interfaces. If that's true, then Views as described look a bit like interface injection + default methods or extension methods.
 
Also it'd be nice to consider having match be the minimum syntactic sugar for making first-class patterns (patterns as values) user friendly, like in this newspeak paper.

Paul

On Wed, Nov 11, 2015 at 4:09 PM kc <kevin...@gmail.com> wrote:
On Tuesday, November 3, 2015 at 3:48:44 PM UTC, Joel Trottier-Hébert wrote:
Yeah I've seen that. I've also opened an issue for the SDK more than a year ago, and they closed it so ... :P

Lars Bak is half-right and half-wrong. 

Right - simple semantics are good. No magic or weird deopts/insecurity.
Wrong - expressiveness (without boilderplate) is important. A switch with weird/tricky limitations from 1972 seems wrong when compared with what other  (lower level!) langs are offering.

K.

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new

To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.

Daniel Joyce

unread,
Nov 12, 2015, 1:41:36 PM11/12/15
to General Dart Discussion
I'm sorry but this looks like the thread for "Design the ugliest language possible". A boolean for changing how Switch behaves? Really? Really!?

Pattern matching should be seperate from Switch. Switch shouldn't either be 'uglified' or removed from Dart 2.0 ( breaking change yes, but that's the point of 2.0 )

--
Daniel Joyce

The meek shall inherit the Earth, for the brave will be among the stars.

Jan Mostert

unread,
Nov 12, 2015, 3:08:12 PM11/12/15
to General Dart Discussion
@Daniel, the boolean was intended to show that the switch in the example was fall-through / non-fall-through and also to not have to write break after each case statement in my examples and at the same time suggesting that we have some mechanism to be able to switch off fall-through.
Without some sort of a flag, you'll need a separate keyword for a normal switch-with-fall-through and switch-without-fall-through, so see it more as pseudo-code.

The rest of my examples were to show that the switch syntax is not fully utilized - switch(argument){}

if argument is of type int / bool / enum, compile it like a normal switch would be compiled.
else if the class implements a switch interface, you can implement how it would compare values in the case statements.

class Regex implements Switch {
    case (argument) -> return this.regexmatches(argument);
}

Now if you do switch (Regex.match(argument)), you have regex capabilities inside a normal switch;

or my own custom class if I want to use a switch for something more complex.

class ABC implements Switch {
    case (argument) -> returns argument != null && argument.contains(argument) && argument.contains("abc");
}

switch (new ABC("test string abc")){
    case "foo": print("contains foo and abc"); break;
    case "bar": print(contains bar and abc"); break;
    case "test": print(contains test and abc"); break;
    default: print("bleh");
}

Having some way to implement how a class is handled inside a switch statement and having default implementations for classes like regex and string would not be too different from how Java allows you to override how classes are compared when using a.equals(b);

public class Student implements Comparable { 
    
private int id;
    
private String name;
    
private int age;
    
public int compareTo(Student otherStudent) {
       
return (this.id < otherStudent.id ) ? -1(this.id > otherStudent.id) ? 1:0 ;
    
} 
}

Creating a special keyword simply to do regular expressions in a similar manner to switch-case seems bloated - if we now want switch-case like behaviour on classes, are we going to create another keyword for that?


Bob Nystrom

unread,
Nov 12, 2015, 4:11:58 PM11/12/15
to General Dart Discussion
On Tue, Nov 3, 2015 at 11:53 AM, Rasmus Eneman <Ras...@eneman.eu> wrote:
The expression route is pretty interesting, say if we added a hasMatch(other) method to Object that the match expression calls to check if the current pattern matches, implemented as other == this. Then we have support for matching simple values. RegExp already overrides hasMatch which means that we also have support for matching against regexes. It can be expanded by libraries with ranges and other stuff. To support type tests or comparisons bool could override hasMatch as other is bool ? other == this : other and we could have patterns like x is num or x < 10.

Yeah, being able to pattern match on arbitrary predicates is pretty interesting. Though, for me, what I care about most at first is being able to pattern match on type. One of my goals is to get rid of some of the redundant type test and bind code you have to do today in Dart like:

if (foo.bar is Baz) {
  var bar = foo.bar as Baz;
  ...
}

Pattern-matching would combine those two operations into a single test-and-bind.
 
What's tricky in this route is declaration, as we use the scope variables can't just be declared to the value we are matching. This could be solved by variables being declared with var but that start to become pretty different from other languages with pattern matching. Another problem is destructuring of lists, if we use brackets justs like list literals (and like array destructuring in ES2015) we would have a hard time distinguishing them from list literals.

My feeling is that a pattern should be its own syntactic form, and not just an expression. Otherwise, things like destructuring get really hard. If we want to also support predicates/guards where you do have a test expression (and not a test pattern) we could have a different syntax to distinguish that.

For example, Scala uses "if" after a pattern and Haskell uses "|" to separate out the guard expression from the rest of the pattern.

Cheers!

– bob

Bob Nystrom

unread,
Nov 12, 2015, 4:13:14 PM11/12/15
to General Dart Discussion

On Wed, Nov 11, 2015 at 10:20 AM, 'Paul Brauner' via Dart Misc <mi...@dartlang.org> wrote:
Whatever syntax/semantics we choose, I wish it was based on some view mechanism like in Scala and F# (or Haskell with extensions) which is allows to foreserving abstractions. That is, you don't have to know the concrete type of some object in order to match against it. Also it'd be nice to consider having match be the minimum syntactic sugar for making first-class patterns (patterns as values) user friendly, like in this newspeak paper.

Yeah, I'm generally a fan of user-extensibility so something along these lines seems reasonable. I don't know how much I care about first-class patterns specifically, but I do think user-defined destructuring is important. If I recall, the C# proposal for pattern matching offers something similar.

Cheers!

– bob

Paul Brauner

unread,
Nov 13, 2015, 3:24:31 AM11/13/15
to mi...@dartlang.org
On Wed, Nov 11, 2015 at 8:39 PM 'Justin Fagnani' via Dart Misc <mi...@dartlang.org> wrote:
On Wed, Nov 11, 2015 at 10:20 AM, 'Paul Brauner' via Dart Misc <mi...@dartlang.org> wrote:
Whatever syntax/semantics we choose, I wish it was based on some view mechanism like in Scala and F# (or Haskell with extensions) which is allows to foreserving abstractions. That is, you don't have to know the concrete type of some object in order to match against it.

Why would you have to know the concrete type? I would assume that pattern matching would work against interfaces. If that's true, then Views as described look a bit like interface injection + default methods or extension methods.

I don't know why you would have to know the concrete type, but in many languages who historically implemented pattern matching it is the case. (In SML for instance, patterns of the form x::xs only match things of type 'a list, where list is a concrete type). I'm just saying that we need some mechanism that allows objects to answer "pattern matching requests". User-defined destructuring as Bob puts it later in the thread. Then will in particular be able to match against interfaces. Against any object really.

Erik Ernst

unread,
Nov 13, 2015, 4:22:06 AM11/13/15
to Dart Misc
+1 for smart pattern matching! ;-)

I think it makes sense to allow a pattern matching mechanism to be expressive, and at the same time tolerate that it may not be as fast as the traditional low-level switch statement (each of them may be the best choice, depending on the situation). Presumably, this means that any given pattern corresponds to a decision tree: For any given scrutinee object the decision ("does it or does it not match this pattern?") will be taken by traversing this tree, invoking methods on the scrutinee at each node; each test may bind a name to the current scrutinee, and each step from a node to the next one will potentially obtain a "subobject" from the current scrutinee which will then be the next scrutinee in that subtree. All these actions will be carried out using standard method invocation, so any object that satisfies the protocol can be scrutinized. It's entirely up to the scrutinee to decide whether it wants to deliver "an actual subobject" (maybe that means the value of a field with the name that we are matching against) or some other object when stepping into a subtree, but some syntactic sugar would be nice in order to make "something obvious" an easily achievable default.

Lasse R.H. Nielsen

unread,
Nov 13, 2015, 5:25:32 AM11/13/15
to mi...@dartlang.org
Much of what you are describing here can be done in code, without language changes.
You want a way to add a user-defined match, deconstruct and bind operation for new classes, so you might as well do that for all classes, and not require language support for some of them.
(Although language support does make the code look more crisp :).

A very quickly written up proof-of-concept: https://dartpad.dartlang.org/e26063664104cd4a5e92

/L
Lasse R.H. Nielsen - l...@google.com  
'Faith without judgement merely degrades the spirit divine'
Google Denmark ApS - Frederiksborggade 20B, 1 sal - 1360 København K - Denmark - CVR nr. 28 86 69 84

Paul Brauner

unread,
Nov 13, 2015, 5:29:09 AM11/13/15
to mi...@dartlang.org
That's what I meant by "having match be the minimum syntactic sugar for making first-class patterns (patterns as values) user friendly". You need language support for introducing real bindings.

Erik Ernst

unread,
Nov 13, 2015, 5:54:02 AM11/13/15
to Dart Misc
Cool, Lasse! And, Paul: Yes, I had actual language bindings in mind. But the example gets close to showing how it would work, using a `Map<Symbol, dynamic>` to represent the binding environment and a `List` (with a fixed order and number of elements) to represent the scrutinee's fields. Now we just need something similar for real! ;-) 

Lasse R.H. Nielsen

unread,
Nov 13, 2015, 6:25:48 AM11/13/15
to mi...@dartlang.org
We do have one way of generating bindings at runtime: Function.apply.

It still requires parameters to be named. Converting the bindings to positions is possible, but probably confusing and error prone. The alternative would be to have a way to specify an index for the matcher binding.

/L

Erik Ernst

unread,
Nov 13, 2015, 6:37:30 AM11/13/15
to Dart Misc
I don't think we have to consider this to be a mechanism for generating bindings at runtime, it could as well be syntactic sugar for a rewriting of the patterns into something that calls some methods and uses `if` or similar constructs to choose the right piece of code to execute, and sprinkles some normal local variable declarations in the code (final, presumably, initialized with the relevant sub-object-extraction invocations), such that the body of each case can use the names used in the pattern. It takes some collaboration from the compiler to be able to distinguish a fresh name (which is "declared" by the pattern) and an existing name (which would be a constraint: "if your subobject at this location is `==` to the value of this name, proceed"), but it would mostly be a syntactic rewrite.

Rasmus Eneman

unread,
Nov 13, 2015, 8:23:57 AM11/13/15
to Dart Misc
While it's not ready for submitting to the committee yet we thought that we should post our current draft of the DEP as it might be interesting for the discussion
https://github.com/drager/dep-pattern-matching/blob/master/proposal.md

You received this message because you are subscribed to a topic in the Google Groups "Dart Misc" group.
To unsubscribe from this topic, visit https://groups.google.com/a/dartlang.org/d/topic/misc/MJXLjFxZu0A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to misc+uns...@dartlang.org.



--
Rasmus Eneman

Lasse R.H. Nielsen

unread,
Nov 13, 2015, 10:16:40 AM11/13/15
to mi...@dartlang.org
On Fri, Nov 13, 2015 at 2:23 PM, Rasmus Eneman <Ras...@eneman.eu> wrote:
While it's not ready for submitting to the committee yet we thought that we should post our current draft of the DEP as it might be interesting for the discussion
https://github.com/drager/dep-pattern-matching/blob/master/proposal.md

It's interesting. It's only destructuring of lists and maps, not custom values, but I do like the "rest" pattern (and we should have rest-parameters on functions too!).

The [a, a] pattern needs to be specified clearly. That is, something like: "if the same identifier binding occurs multiple times in the same pattern, it corresponds to an extra condition that all the values matched by these identifier patterns are equal". Or identical? 

Pattern matching usually occurs in functional languages where values are immutable, so equal and identical is the same thing. In Dart, you can have equal objects that are not identical, so would the pattern [a, a] match [new Symbol("a"), new Symbol("a")]. 
It probably needs to be "equals" because that's what you will expect for string values, and then you need to specify which of the equal values is bound to "a".


For type tests, I have been thinking in terms of type annotations - which would be slightly misleading until we get non-null-by-default types. That is, instead of [x is int, y is int], the pattern would be [int x, int y].
That makes it possible to see  a normal variable declaration "int x = 42;" as a pattern matched with a value, and we could consider generalizing it to [var x, var y] = [2, 4].

Erik Ernst

unread,
Nov 13, 2015, 10:50:55 AM11/13/15
to Dart Misc
Interesting! The [a, a] patterns and other features that introduce equality constraints are somewhat tricky, though: If you can say 'match (x) { a as List { next: a } => .. }' then the pattern matcher is required to check that the whole list is identical to its tail (i.e, sublist(1)). This is true for a cyclic list, so you cannot just compile that down to "don't ever choose this case", but you have to be careful in order to know that you're done, and that's a lot of algorithmic magic to expect from your compiler and/or runtime system. ;-)

Jan Mostert

unread,
Nov 13, 2015, 1:15:08 PM11/13/15
to Dart Misc
Syntax looks awesome, especially Object and List matching!



Daniel Joyce

unread,
Nov 13, 2015, 2:59:23 PM11/13/15
to Dart Misc

In most languages calling symbol twice with same value returns the exact same instance of symbol. If this is not true in dart it needs to be fixed.

Rasmus Eneman

unread,
Nov 13, 2015, 3:12:20 PM11/13/15
to Dart Misc
>It's interesting. It's only destructuring of lists and maps, not custom values, but I do like the "rest" pattern (and we should have rest-parameters on functions too!).
It's actually lists and objects. Maps could be added as well but I don't know if you use Maps in this way in Dart?
And I would love rest-parameters :)

>The [a, a] pattern needs to be specified clearly. That is, something like: "if the same identifier binding occurs multiple times in the same pattern, it corresponds to an extra condition that all the values matched by these identifier patterns are equal". Or identical?

Oh, yeah. That's a big omission. The current idea is equality, like if you had specified a literal.


>Interesting! The [a, a] patterns and other features that introduce equality constraints are somewhat tricky, though: If you can say 'match (x) { a as List { next: a } => .. }' then the pattern >matcher is required to check that the whole list is identical to its tail (i.e, sublist(1)). This is true for a cyclic list, so you cannot just compile that down to "don't ever choose this case", but you >have to be careful in order to know that you're done, and that's a lot of algorithmic magic to expect from your compiler and/or runtime system. ;-)
With the current idea that would transpile to (if we say that a is in scope)
if (a is List && a.next == a)
personally I don't think you should have something similar to (head, tail) of functional languages where head is the first element and tail is the rest of the list on a syntactical plane. Instead I would prefer adding a "tail" getter to List.

Erik Ernst

unread,
Nov 16, 2015, 8:41:57 AM11/16/15
to Dart Misc
On Fri, Nov 13, 2015 at 9:12 PM, Rasmus Eneman <Ras...@eneman.eu> wrote:
>It's interesting. It's only destructuring of lists and maps, not custom values, but I do like the "rest" pattern (and we should have rest-parameters on functions too!).
It's actually lists and objects. Maps could be added as well but I don't know if you use Maps in this way in Dart?
And I would love rest-parameters :)

>The [a, a] pattern needs to be specified clearly. That is, something like: "if the same identifier binding occurs multiple times in the same pattern, it corresponds to an extra condition that all the values matched by these identifier patterns are equal". Or identical?

Oh, yeah. That's a big omission. The current idea is equality, like if you had specified a literal.

>Interesting! The [a, a] patterns and other features that introduce equality constraints are somewhat tricky, though: If you can say 'match (x) { a as List { next: a } => .. }' then the pattern >matcher is required to check that the whole list is identical to its tail (i.e, sublist(1)). This is true for a cyclic list, so you cannot just compile that down to "don't ever choose this case", but you >have to be careful in order to know that you're done, and that's a lot of algorithmic magic to expect from your compiler and/or runtime system. ;-)
With the current idea that would transpile to (if we say that a is in scope)
if (a is List && a.next == a)
personally I don't think you should have something similar to (head, tail) of functional languages where head is the first element and tail is the rest of the list on a syntactical plane. Instead I would prefer adding a "tail" getter to List.

Ah, my intention was simply to illustrate what happens if we use patterns that imply equality constraints (you just confirmed that it was intended to mean equality, that is `==` and not `identical`), if they are combined with recursion in types.

So I should not have used `List` as the example, because a Dart list actually has a different structure. A suitable definition would be something very basic like `class MyList { MyList next; .. }`, where we use recursion in the type (i.e., `MyList` contains a field of type `MyList`). Equality would be defined structurally on the list (and it would use whatever `operator ==` does on the elements because it has no choice, but it doesn't matter for this argument whether the equality on elements is structural or identity-based).

With that setup, a pattern like `match (x) { a as MyList { next: a } => .. }` would correspond to the criterion "succeed if `a` is a `MyList` which is equal to its tail", and then the rest of my explanations would remain unchanged. (That is, with a finite heap only a cyclic `MyList` could ever satisfy that criterion, but it's questionable whether a language mechanism should be required to include enough algorithmic smarts to be able to handle such a situation gracefully).

In summary, my message was something like "equality constraints may be too expensive for a language mechanism, so we probably don't want that". ;)

Paul Brauner

unread,
Nov 16, 2015, 8:55:09 AM11/16/15
to Dart Misc
I agree, non-linear patterns are a gadget in my opinion. They are superseded by guards, which don't force you to bake one particular notion of equality into the semantics of the language. 

Bob Nystrom

unread,
Nov 18, 2015, 12:07:57 PM11/18/15
to General Dart Discussion

On Mon, Nov 16, 2015 at 5:54 AM, 'Paul Brauner' via Dart Misc <mi...@dartlang.org> wrote:
I agree, non-linear patterns are a gadget in my opinion. They are superseded by guards, which don't force you to bake one particular notion of equality into the semantics of the language. 

+1.

The simplest, safest option is to have it be a syntax error for a variable to appear twice in the same pattern. I don't think allowing repetition there adds much value (like Paul says, you can always just guard on equality) and I think it adds a lot of complexity.

It also makes patterns work less like variable declarations, which I think is generally a bad idea. Variable declarations basically are patterns (or a special subset of them) so I think we should encourage them to converge as much as possible.

Cheers!

– bob



Rasmus Eneman

unread,
Nov 22, 2015, 8:11:23 AM11/22/15
to Dart Misc
I updated it now to be a syntax error, as you said it's simple to do anyway, no need to complicate the spec or expectations on behavior.
One pattern I'm still unsure about though is Point {x, y: x} where the second x is clearly not being declared, should that be allowed?
It's still easy to do with a guard but it might be expected that it would be allow because that difference of literal and variable is never done
anywhere else in the language.

One other thing is p as Point {x, y} which you have used as a pattern above. As currently written it is not allowed, but could be easily added.
I have no opinion on either but I think that it might be unnecessary as in most cases you would have the object being matched in a variable
that is in scope anyway.

I also added initial wording on warnings. There probably should be more but it's a start at least.

I think Scalas extractor objects needs to be mentioned. Personally I don't see much of a value with them when there are object destructuring
but there are probably other opinions out there. But all classes that would use them should be able to add getters for those values instead.

For example Some/None could be used like:
match (x) {
  Some {value} => print('got $value');
  None {} => print("it's empty");
}

--
For other discussions, see https://groups.google.com/a/dartlang.org/
 
For HOWTO questions, visit http://stackoverflow.com/tags/dart
 
To file a bug report or feature request, go to http://www.dartbug.com/new
---
You received this message because you are subscribed to a topic in the Google Groups "Dart Misc" group.
To unsubscribe from this topic, visit https://groups.google.com/a/dartlang.org/d/topic/misc/MJXLjFxZu0A/unsubscribe.
To unsubscribe from this group and all its topics, send an email to misc+uns...@dartlang.org.



--
Rasmus Eneman

Rasmus Eneman

unread,
Nov 22, 2015, 3:33:03 PM11/22/15
to Dart Misc
I just realized that it may be hard to get static warnings for exhaustion to work as intended with


match (x) {
  Some {value} => print('got $value');
  None {} => print("it's empty");
}

if the static type of x is Option and we have the type hierarchy

abstract class Option<T> {}
class Some<T> extends Option<T> {}
class None extends Option {}

Then the only way for the analyzer to realize that the above expression fully exhausts x is to know all subclasses of Option.
If Dart had type aliases and union types it could have been defined as

type Option<T> = Some<T> | None;
class Some<T> {}
class None {}

But the only options I see now is
1. Issue a warning which forces people to add a default case that is never reached, or use the default case for None which
    would make it harder to understand the intent.
2. Don't issue warnings at all
3. Don't issue warnings for other cases than when the static type of x is an enumerable.

What I'm leaning towards is option 3 and add warnings for everything else if Dart ever gets union types and the above
case becomes possible for the analyzer to understand without knowing all subclasses of a class.

--
Rasmus Eneman

kc

unread,
Nov 23, 2015, 8:37:51 AM11/23/15
to Dart Misc
ES6 uses {} for object literals. And Strong Mode/SoundScript makes them more record-like - especially with the new 'proper' Map object - which may get literal syntax.

Dart followed Python and used {} for Map. (Another area where 'familiar' Dart deviates from both JS and Java/C#).

So I'm not sure how appropriate it is to use {} for de-structuring/pattern matching.

Also Dart could use something in the tuple/object literal space which plays well with an eventual pattern matching proposal.

K.

On Saturday, October 31, 2015 at 2:14:00 PM UTC, Rasmus Eneman wrote:
Me and a friend started working on a DEP for pattern matching using a Rust like match expression however, as we were creating examples and translated them to actual Dart code we saw that the big benefit is as a replacement for the switch-statement. They can provide nice syntactic suger for multiple if else branches, but is that enough to introduce a new concept?
However switch statements are a pretty complex concepts with fall though, or in Darts case not which already breaks with other languages.
To re-add fall though Dart do have labels which is another concept for a very limited use case.

Therefore I would like to raise the discussion if the switch statement should be dropped for Dart 2.0 and instead be replaced with a Rust like match expression.

When compared to switch statements the match expression does have a nicer syntax which is more similar to the rest of the language (switch the only statements where blocks starts with a colon and ends by magic),
and is simpler because there no fall though or labels (that behaviour can easily be replaced with function calls anyway) and does not break semantics with other popular languages.

The places were it would be a bit nicer than if/else would only come as an additional bonus.

Anders Holmgren

unread,
Nov 23, 2015, 10:29:29 PM11/23/15
to Dart Misc
Objects in js are just maps with a bit of sugar aren't they?

Erik Ernst

unread,
Nov 24, 2015, 7:48:12 AM11/24/15
to Dart Misc
With guards, which would be able to do anything because they are just boolean expressions (even though it would be bad style to have side-effects), it very quickly gets impossible to check for exhaustive matching anyway. E.g., if you can show that `e` is pure then `e` and `!e` will be an exhaustive set of cases, but in general it's of course an undecidable problem.

So the no-warnings approach would be forced in some cases, and warnings would then necessarily be confined to some "nice" subset of all the possible match expressions. On the other hand, it makes sense to detect such a "nice" subset if we can come up with something that is easy to understand.


You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.



--

Erik Ernst

unread,
Nov 24, 2015, 8:00:34 AM11/24/15
to Dart Misc
On Sun, Nov 22, 2015 at 2:11 PM, Rasmus Eneman <Ras...@eneman.eu> wrote:
I updated it now to be a syntax error, as you said it's simple to do anyway, no need to complicate the spec or expectations on behavior.
One pattern I'm still unsure about though is Point {x, y: x} where the second x is clearly not being declared, should that be allowed?

That's probably just as unmanageable as the equality constraints. In the general case, this allows us to require that, say, the left and right subtree of a `Tree` node must be equal to each other (just use `Tree` rather than `Point`). That might be defined in terms of structural equality, which would entail a traversal and comparison of arbitrarily large structures which might be overlapping and/or cyclic. Just think about

  var t = new Tree(new Tree(t, t), t);

(ignoring that we'd need spell it out as a couple of statements involving a destructive update in order to actually create such a cyclic structure). That tree has identical "infinite" (that is, cyclic) right and left subtrees, but since the right subtree is always returning to the top level one step faster than the left subtree, it gets tricky to know when we can stop and say "true", respectively how we can avoid going into an infinite loop. We should leave that kind of traversal/checking to the domain programmer, not the language.
 
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.



--

Burak Emir

unread,
Nov 24, 2015, 8:29:01 PM11/24/15
to mi...@dartlang.org
Some comments:

* I'd keep the 'case' in match expressions. Consider i.e. exp match { case x  => (y) => z case 1 => 2 case _ => ... } without the 'case'.

* exhaustiveness checking for testing subtypes works if you can assume a closed world (e.g. knowledge of all subtypes).

In Java, Scala, ML, things like separate compilation or dynamic class-loading break that assumption, so Scala introduced a keyword "sealed" that would forbid subtypes other than the ones known. I'm not sure what the plan is for dart.

* there are also situations where exhaustiveness checking may be undesirable; for instance one may define classes that represent a rich set of operators of propositional logic, but then after some point, they all get translated to some subset (say NOT and AND). Another example is abstract syntax tree classes in compilers where earlier passes translate away / desugar constructs into simpler ones. Finally, extending an enum with a new value, that can only appear in a certain place. Consequently, it would be good to have a way to suppress the warning.

* about the added value of extractors aka "unapply" methods. These give users an easy way to define their own patterns *and give them a name*, hence it does not make much sense to compare it with object destructuring.  E.g. consider the following, with suitable SELECT, FROM and WHERE extractors:
foo(String queryString) => queryString match { case SELECT(x, FROM(y, WHERE, condition))) => ... } 

or a network protocol's pattern like: byteIterable match { case MESSAGE(header, payLoadSize, payLoadBytes) => }

* exaustiveness checking won't easily be extended to extractors, though.

* The DEP makes me curious: what happens if one wants to match against generic types? Would you support type variable binding or type variable checking in addition to variable binding?

<T> foo(x) => x match { case y is List<T> => new Set<T>..addAll(y) }

* one thing that is frequently found in match statements is the ability to match against variables declared in scope. So with a suitable extractor "twice", we may want to check that y is 2*x, however we need a way to reference a variable (instead of declaring a new variable x). In Scala, variables from outside had to be capitalized, i.e. 

foo(String X) => (y) => y match { case twice(X) => /* we now know that y == 2 * X */ }

cheers,
Burak

Paul Brauner

unread,
Nov 25, 2015, 4:01:48 AM11/25/15
to mi...@dartlang.org
On Wed, Nov 25, 2015 at 2:29 AM 'Burak Emir' via Dart Misc <mi...@dartlang.org> wrote:
Some comments:

* I'd keep the 'case' in match expressions. Consider i.e. exp match { case x  => (y) => z case 1 => 2 case _ => ... } without the 'case'.

* exhaustiveness checking for testing subtypes works if you can assume a closed world (e.g. knowledge of all subtypes).

In Java, Scala, ML, things like separate compilation or dynamic class-loading break that assumption, so Scala introduced a keyword "sealed" that would forbid subtypes other than the ones known. I'm not sure what the plan is for dart.

* there are also situations where exhaustiveness checking may be undesirable; for instance one may define classes that represent a rich set of operators of propositional logic, but then after some point, they all get translated to some subset (say NOT and AND). Another example is abstract syntax tree classes in compilers where earlier passes translate away / desugar constructs into simpler ones. Finally, extending an enum with a new value, that can only appear in a certain place. Consequently, it would be good to have a way to suppress the warning.

* about the added value of extractors aka "unapply" methods. These give users an easy way to define their own patterns *and give them a name*, hence it does not make much sense to compare it with object destructuring.  E.g. consider the following, with suitable SELECT, FROM and WHERE extractors:
foo(String queryString) => queryString match { case SELECT(x, FROM(y, WHERE, condition))) => ... } 

or a network protocol's pattern like: byteIterable match { case MESSAGE(header, payLoadSize, payLoadBytes) => }

* exaustiveness checking won't easily be extended to extractors, though.

* The DEP makes me curious: what happens if one wants to match against generic types? Would you support type variable binding or type variable checking in addition to variable binding?

<T> foo(x) => x match { case y is List<T> => new Set<T>..addAll(y) }

* one thing that is frequently found in match statements is the ability to match against variables declared in scope. So with a suitable extractor "twice", we may want to check that y is 2*x, however we need a way to reference a variable (instead of declaring a new variable x). In Scala, variables from outside had to be capitalized, i.e. 

foo(String X) => (y) => y match { case twice(X) => /* we now know that y == 2 * X */ }


I would rather use a guard for this, I find this feature of scala confusing :)

Erik Ernst

unread,
Nov 25, 2015, 4:03:01 AM11/25/15
to Dart Misc
Cool stuff, Burak! ;-)

Type variable binding would be difficult in Dart, I guess. For instance, we would expect a `List` (that is, `List<dynamic>`) to match a typing requirement specified as `List<List<T>>`, but how would we then come up with a suitable T, presumably extracting it from somewhere inside the black hole called `dynamic`? Maybe we could use `dynamic`, but we would certainly have to be careful. 

We could also have an upper bound on the type variable that we are trying to extract from somewhere inside `dynamic`, and it might be an F-bound (say, the type variable is declared as `X extends Foo<X, Y>` along with `Y extends Bare<X, Y>`); again, we might get away with "X is dynamic, and so is Y".

Hence, it looks like we could often end up in a branch of the pattern matching construct where we _think_ we know a lot about the situation, but in practice we are just looking at something whose typing characteristics are very loose..
Reply all
Reply to author
Forward
0 new messages