"has match" operator for improved switch-case statement

36 views
Skip to first unread message

Sean Eagan

unread,
Mar 7, 2012, 5:34:41 PM3/7/12
to General Dart Discussion
So here's the idea:

At the language level: 

* define a "has match" operator, possibly denoted as ~=
* change switch statements to check `switchExpression ~= caseExpression` instead of `switchExpression == caseExpression`

At the library level, define:

// in Object
bool operator ~=(Object other) => this == other;

// in Pattern
bool operator ~=(Object other) => this.hasMatch(other); // or just replace hasMatch method

// in Function
bool operator ~=(Object other) => this(other);

// in Collection
bool operator ~=(Object other) => this.contains(other); // see http://dartbug.com/1030

// in Class/ClassMirror (interface representing a class/interface)
bool operator ~=(Object other) => other is this; // or possibly this.hasInstance(other)


I'll send out some examples of what this would allow later.

Cheers,
Sean Eagan

Sean Eagan

unread,
Mar 8, 2012, 9:54:32 AM3/8/12
to General Dart Discussion


On Wed, Mar 7, 2012 at 4:34 PM, Sean Eagan <seane...@gmail.com> wrote:
* change switch statements to check `switchExpression ~= caseExpression`

should be `caseExpression ~= switchExpression`

// in Pattern
bool operator ~=(Object other) => this.hasMatch(other); // or just replace hasMatch method 

should be in Regexp, not Pattern, since Strings should only match themselves, not substrings.

// in Collection
bool operator ~=(Object other) => this.contains(other); // see http://dartbug.com/1030

should instead treat each of its items as a pattern:

bool operator ~=(Object other) => some((E item) => item ~= other);
 
Cheers,
Sean Eagan

Michael Hendricks

unread,
Mar 8, 2012, 10:02:44 AM3/8/12
to Sean Eagan, General Dart Discussion
On Wed, Mar 7, 2012 at 3:34 PM, Sean Eagan <seane...@gmail.com> wrote:
> This would be similar to Ruby's case statement
> (http://www.skorks.com/2009/08/how-a-ruby-case-statement-works-and-what-you-can-do-with-it/)

FWIW, Perl uses a similar operator (~~ called "smart match") as the
basis for its switch statement (given-when). See
http://perldoc.perl.org/perlsyn.html#Switch-statements I've found it
to be a very powerful and clear way of expressing flow control.
Nearly every non-trivial Perl program I write uses a given-when
construct.

With Dart's operator overloading, Sean's proposal would be especially
powerful. I like it.

--
Michael

Ladislav Thon

unread,
Mar 8, 2012, 10:15:01 AM3/8/12
to Michael Hendricks, Sean Eagan, General Dart Discussion
With Dart's operator overloading, Sean's proposal would be especially
powerful.  I like it.

I like it too, if it's worth anything :-) I'd use it in my matchers library in the very moment it gets implemented in Dart VM.

I can live without that, though. And if it should get too smart like in other languages (where matching a RegExp doesn't actually return boolean value but an array of matches instead), I'd rather prefer not to have it.

LT

Sean Eagan

unread,
Mar 8, 2012, 10:22:49 AM3/8/12
to Michael Hendricks, General Dart Discussion
Hi Michael,

~~ would probably work too, and there are probably other operators which would work.  Also, if we didn't want to introduce a new operator, it could even just be a traditional named method such as "hasMatch" or something.  One issue I thought of is if you override == to take a specialized type on the RHS, then if you want proper type checking of RHS's of ~= (or whatever its called) you would need to override it as well to take a specialized type, unless the type checker can infer from Object#~= return expression (i.e. this == other) that the arguments type should be specialized.

--
Sean Eagan

Eric Leese

unread,
Mar 8, 2012, 6:38:06 PM3/8/12
to General Dart Discussion
Not sure how I feel about every class defining its own (potentially
surprising) switching behavior. I'd rather have a syntax to define at
the switch statement what operator to use when I don't want ==,
preferably allowing any method on the object to be used:

switch (obj) === {
case obj1:
case obj2:
}

switch (obj) is {
case String:
case List<int>:
}

switch (string) startsWith {
case "a":
// ...
}

Which could more clumsily be written as:

switch (string.startsWith) () {
case "a":
// ...
}

which would create the closure string.startsWith, then do the switch
using its call operator. Don't write it that way, please! But having
a general way such as this to use a closure as a predicate and switch
to find out which case makes it true could be useful.

And if the function you want is on the case objects:

switch hasMatch(str) {
case regex1:
case regex2:
}

switch contains(element) {
case set1:
}

On Mar 8, 7:22 am, Sean Eagan <seaneag...@gmail.com> wrote:
> Hi Michael,
>
> ~~ would probably work too, and there are probably other operators which
> would work.  Also, if we didn't want to introduce a new operator, it could
> even just be a traditional named method such as "hasMatch" or something.
>  One issue I thought of is if you override == to take a specialized type on
> the RHS, then if you want proper type checking of RHS's of ~= (or whatever
> its called) you would need to override it as well to take a specialized
> type, unless the type checker can infer from Object#~= return expression
> (i.e. this == other) that the arguments type should be specialized.
>
>
>
>
>
>
>
>
>
> On Thu, Mar 8, 2012 at 9:02 AM, Michael Hendricks <mich...@ndrix.org> wrote:
> > On Wed, Mar 7, 2012 at 3:34 PM, Sean Eagan <seaneag...@gmail.com> wrote:
> > > This would be similar to Ruby's case statement
> > > (
> >http://www.skorks.com/2009/08/how-a-ruby-case-statement-works-and-wha...
> > )

Josh Gargus

unread,
Mar 8, 2012, 7:48:57 PM3/8/12
to Eric Leese, General Dart Discussion
I like Eric's idea.  Is there a way to make it work with an arbitrary 2-argument predicate, where the first argument would be bound to the switch argument, and the second to the case values?  Eg:

bool function pred(one, two) {
// Obviously, do something more interesting here.
return one == two;
switch (obj) pred {
case "a":
//...
}

This seems problematic, because obj might also have a method named "pred".  Is this surmountable?  How desirable would this be?

Cheers,
Josh

Sam McCall

unread,
Mar 8, 2012, 9:30:02 PM3/8/12
to Josh Gargus, Eric Leese, General Dart Discussion
Since the switch value is constant, isn't the first parameter always
going to be obj?
Maybe we should have a second 'which' statement, which takes a
predicate and applies it to a list of values:

which ((pattern) => pattern.matches(input)) {
case RANDOM_GIBBERISH: smackMonkey();
case VALID_POEM: dispenseBanana();
default: issueEncouragement("Nearly there!");

Eric Leese

unread,
Mar 8, 2012, 9:51:51 PM3/8/12
to General Dart Discussion
Okay, I think I have a better syntax for this. Two syntaxes,
actually. Basically, the point of a switch statement is to clean up a
series of if, else if, where the ifs have the same structure. So
these sorts of switches may be more readable if they mimic the
structure of the if statement they are replacing.

So my first idea is to support using the case keyword within the
switch expression, and when it appears one or more times in the switch
expression, the expression is understood to be a boolean expression
where each case value will be inserted in place of the word case:

switch (string.startsWith(case)) {
case "a":
// ...
}

switch (case.contains(element)) {
case set1:
}

switch (val < case + .5 && val >= case - .5)
{
case 1.0:
case 2.0:
}

The second syntax is for expressions that do not contain the keyword
case. For these the case statements take the form "case op?
expression" where op is any relational operator (ideally is is
supported as well). If the operator is left out, it is ==.

switch(val) {
case === obj:
case is! int:
case < 0:
case 0:
case >= 1:
}

Your tastes may differ, but it seems more obvious to me what these
switches mean than with the syntax I suggested before.
> > > >http://www.skorks.com/2009/08/how-a-ruby-case-statement-works-and-wha.<https://www.google.com/url?sa=D&q=http://www.skorks.com/2009/08/how-a....>
> > ..
> > > > )
>
> > > > FWIW, Perl uses a similar operator (~~ called "smart match") as the
> > > > basis for its switch statement (given-when).  See
> > > >http://perldoc.perl.org/perlsyn.html#Switch-statements<https://www.google.com/url?sa=D&q=http://perldoc.perl.org/perlsyn.htm...> I've

Sam McCall

unread,
Mar 8, 2012, 10:28:36 PM3/8/12
to Eric Leese, General Dart Discussion
On Fri, Mar 9, 2012 at 3:51 AM, Eric Leese <le...@google.com> wrote:
> Okay, I think I have a better syntax for this.  Two syntaxes,
> actually.  Basically, the point of a switch statement is to clean up a
> series of if, else if, where the ifs have the same structure.  So
> these sorts of switches may be more readable if they mimic the
> structure of the if statement they are replacing.
>
> So my first idea is to support using the case keyword within the
> switch expression, and when it appears one or more times in the switch
> expression, the expression is understood to be a boolean expression
> where each case value will be inserted in place of the word case:
>
> switch (string.startsWith(case))  {
>  case "a":
>  // ...
> }
>
> switch (case.contains(element)) {
>  case set1:
> }
>
> switch (val < case + .5 && val >= case - .5)
> {
>  case 1.0:
>  case 2.0:
> }
+1, I really like this.

My only hesitation is that case looks like a variable embedded in an
arbitrary expression, but since it's not a variable,
speccing/implementing this behaviour might be complex. e.g. consider:
switch ((){ switch(x == case) { case 42: ... } }()) {
}
'Obviously' the 'case' being compared to x is from the inner switch,
but only because we know the name-shadowing rules for variables...
The easiest way to solve this might be to turn case into a real
auto-bound variable (maybe called _, since case is a keyword).

> The second syntax is for expressions that do not contain the keyword
> case.  For these the case statements take the form "case op?
> expression" where op is any relational operator (ideally is is
> supported as well).  If the operator is left out, it is ==.
>
> switch(val) {
>  case === obj:
>  case is! int:
>  case < 0:
>  case 0:
>  case >= 1:
> }

I don't think this is so useful, because lots of my common use cases
aren't operators (or are operators on the case-value rather than the
switched-value).

What about just switch {...} defaulting to switch(true) {...}

switch {
case val.isEmpty(): ...
case SOME_PATTERN.matches(val): ...
}

To use this you may have to assign the expression to a local (e.g.
final val = readline()) but that doesn't seem too onerous.

Sean Eagan

unread,
Mar 9, 2012, 1:17:43 PM3/9/12
to Sam McCall, Eric Leese, General Dart Discussion

I like the autobound _ variable idea, but I I think it would be more useful in reverse, i.e. to have it represent the switch-value from within the case-values:

instead of doing:

num x = e;
if(0 <= x && x < 5) {...
} else if(x == 5) {...
} else if(5 < x && x <= 10) {...
} else throw "invalid value";

I could do:

switch(e) {
  case 0 <= _ && _ < 5 : ...
  case 5 : ...
  case 5 < _ && _ <= 10 : ...
  default throw "invalid value": 
}

If _ doesn't appear in a case-expression (traditional case-expressions), then the case expression expands to `_ == caseExpression` (see `case 5` above).

Could also make it explicit and allow any variable name by using a variable declaration as the switch expression:

switch(int _ = e) {
  case 0 <= _ && _ < 5 : ...
  case 5 : ...
  case 5 < _ && _ <= 10 : ...
  default throw "invalid value": 
}

Cheers,
Sean Eagan
Reply all
Reply to author
Forward
0 new messages