Open clsource opened 3 years ago
I think we should enforce the block after the ':' for multiline to follow the logic of the rest of the grammar. Single line statement is a bit confusing, but I would expect it to continue by default like would do an if
statement and not return of the function, so that part of your implementation is a no go for me.
If possible I would prefer to have something more like this:
switch (value) {
== 42: {
System.print("The answer")
System.print("to the universe")
continue
}
.isEven: {
System.print("This is odd")
break
}
is String: System.print("How did we get there") // Implicit continue
else System.print("Karamba")
}
Optionnaly, for ease it would be nice to have a Object::map(fn) { fn.call(this) }
so we can write something like:
switch (value) {
.map {|value| etc... } : etc...
etc...
}
I like the way that @mhermier has done this which is very natural and flexible. It's also easy to cater for ranges and multiple values:
switch (value) {
in 1..6: {
// blah
}
in [7, 10, 12]: {
// blah
}
}
However, my personal preference would be for the case
clauses (whether blocks or single statements) not to fall through at all because, most of the time, this is what you want.
The break
and continue
statements are best employed to break out of or continue a loop enclosing the switch
statement if there is one.
Instead, I'd introduce a second new keyword fallthrough
which would do what it says on the tin. Go uses this word and it's unlikely to have been used much as an ordinary identifier in the past.
Unfortunately, switch
probably will have been used a lot and, although I'd prefer to still use it, a possible alternative here would be when
.
Although I really like complicated pattern matching, I don't think it's a good fit for Wren, and even not generally for dynamic languages (although Python will have them in 3.10).
I vote for simple switch
, but without fallthrough:
switch (a) {
case 1: System.print("1")
case "2":
System.print("1")
System.print("A string")
default: System.print("Nothing of the above")
}
Though I'm not sure this is necessary.
Although a simple switch (without fallthrough) is nice, I'm not sure there's enough value in it to be worth doing.
What I liked about @mhermier's approach is that we can do some simple pattern matching in a natural way.
If we don't bother with fallthrough at all (no great loss IMO), it might even be possible for the compiler to just turn it into an if/else if/else
ladder.
Apart from languages which don't support ranges, fallthrough isn't often needed in my experience and, if it is, you could do stuff like:
switch (value) {
in 1..2: {
if (value == 1) doSomething.call()
doSomethingMore.call()
}
// etc
}
In that case I suggest that the keyword fallthrough
is added, and must be
the last expression in the block. Having switch
nested in loops is pretty
frequent and reusing continue
here would be to much impractical.
Go also insists on fallthrough
being the last statement in a case
clause. In other words, it can't be conditional which is presumably to make the implementation easier.
Well it can be augmented in a second revision. This is a bare minimum so it is working.
I personally struggle a little bit with the word fallthrough
since it has many double letters and gh position. How about pass
?
The problem is that fallthrough
is the commonly accepted term for this behavior. pass
just doesn't have the same meaning - it's used in Python to mean 'do nothing' - and it's a commonly used identifier in any case.
As well as Go, fallthrough
is used by Swift and by C++ 17 (as an attribute). I couldn't find any alternatives at all.
I suppose we could spell it fallthru
or use a made-up word such as nextcase
but neither look right to me. Perhaps if you think of it as two words, which is really what it is, then it'll seem more palatable.
I have some ideas I want to share.
switch (expr) {
| _: System.print("value is a different number (This is default case)")
// Alternative default case
| default: System.print("alternative default case")
| 1, 2, 3: System.print("value is 1, 2, or 3")
| 4: System.print("value is 4 but will fall through to next case")
|> 5: System.print("value may be 4 or 5, because 4 can fall though")
| 6:
System.print("this case...")
System.print("has multiple")
System.print("instructions!")
}
I like the idea of using |>
to indicate falltrought.
My preference would be using it at the right position since using if before can be confused with "greater than".
Another option instead of default
or underscore (_
). Would be using true
since it will always be true regardless of the case.
switch (expr) {
| 1, 2, 3 : System.print("value is 1, 2, or 3")
| 4 |> System.print("value is 4 but will fall through to next case")
| 5 : System.print("value may be 4 or 5, because 4 can fall though")
| 6 :
System.print("this case...")
System.print("has multiple")
System.print("instructions!")
| true : System.print("This is the default case")
}
This more or less can be translated to ifs/elses
if (expr == 1 || expr == 2 || expr == 3) {
return System.print("value is 1, 2, or 3")
}
if (expr == 4 || expr == 5) {
if (expr == 4) {
System.print("value is 4 but will fall through to next case")
}
if (expr == 5) {
return System.print("value may be 4 or 5, because 4 can fall though")
}
return
}
if (expr == 6) {
System.print("this case...")
System.print("has multiple")
return System.print("instructions!")
}
return System.print("This is the default case")
}
What syntax would be used to express the following cases:
Regarding the placing of the default case first in the switch, it's something I've never personally liked even though there may be a performance benefit if the default case is much more common than the other cases.
But would it even be possible in a language such as Wren with a single pass compiler?
I think one of the things I hoped switch statements should do was optimize itself for list of numbers so it isn't checking every option like an if-else ladder. because of that, I was also thinking that we should only hold values in cases instead of expressions (similar to how it is done in C).
I'm unfamilliar with the bytecode for now so this is more my concept rather than a what the VM can actually do right now but...
I guess it may be limiting to not put expressions in a switch statement cases though so maybe we could also have a match
statement or switch statement without an expression passed to it. This could behave more like if-else ladders
something like:
// checks for first truthy value
switch {
| expr > 5: // do stuff
| expre.method: // do other stuff
}
// maybe might reduce ambiguity
match {
| expr > 5: // do stuff
| expre.method: // do other stuff
}
Also, in my opinion, I disagree with each cases interacting with switch statements. I think operators should still have expressions on both sides, type checks should be as they are, and methods should still contain their receivers even in switch cases.
so none of these would work:
switch (expr) {
| >= 5:
| .isOdd:
| > 7: // thus reducing ambiguity with my previous example
| is String:
}
Lastly, I don't like this very much either
Another option instead of default or underscore (_). Would be using true since it will always be true regardless of the case.
It may not be often that people are comparing bools in a switch but it still could be possible and this suggestion brings ambiguity,
var aBool = false
switch (aBool) {
| true: System.print("aBool is definitely true")
| false: System.print("aBool is definitely false")
| 5: System.print("aBool isn't even a bool")
}
Adding a switch
statement was in fact first discussed back in #352 and I think the conclusion then was that it needs to go some way towards pattern matching to be worth doing at all.
Although one can argue about the details, I did feel that @mhermier's variant satisfied that criterion but the other variants discussed did not though I'm grateful to @clsource for giving the matter another airing.
It seems to me that the other variants can't really handle pattern matching without repeating the control variable in each case which negates one of the benefits of having a switch
statement in the first place. The only benefit it really leaves us with is nicer, more readable syntax and the possibility of fallthrough compared to an if/else
ladder.
If some sort of 'jump table' were possible on the lines you suggest, then that would be another reason why a switch
statement might be advantageous though I don't know how feasible that would be in Wren.
I don't understand the point of the |
as a separator, parsing wise at
best it serve as a separator for blocks but it fells out off place for some
reasons...
I don't understand the point of the
|
as a separator, parsing wise at best it serve as a separator for blocks but it fells out off place for some reasons...
I think the |
character as a separator seems similar to saying "or", so something like value1 | value2
is like value1 or value2
. The syntax suggested there is the syntax used in Rust.
In my opinion (and this is just an opinion fueled by my bias so it can be disregarded), I don't want wren to resemble Rust as it's sometimes pretty awkward and Rust traumatizes me ;_;
Also I might add, having the pipe character precede case values may make it easier for the single pass compiler to tell that this is a start of a new case instead of just having variable names, lists, and maps followed by an arrow. Again just guessing as I haven't dug too deep into wren source code.
In all the case _
as default is not an option since it is a valid member
variable name.
In all the case
_
as default is not an option since it is a valid member variable name.
Ah true, I guess if we are putting expressions in the switch statement, might have to use else
or default
for the default case. In my opinion (gah I'm so opinionionated), I prefer default
especially if the default case is not restricted to the last case because putting else sounds like the last case.
Other languages like Elixir have separate simple case conditions from pattern matched ones
https://elixir-lang.org/getting-started/case-cond-and-if.html
case is useful when you need to match against different values. However, in many circumstances, we want to check different conditions and find the first one that does not evaluate to nil or false. In such cases, one may use cond:
iex> cond do
...> 2 + 2 == 5 ->
...> "This will not be true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> 1 + 1 == 2 ->
...> "But this will"
...> end
"But this will"
This is equivalent to else if clauses in many imperative languages (although used much less frequently here).
If all of the conditions return nil or false, an error (CondClauseError) is raised. For this reason, it may be necessary to add a final condition, equal to true, which will always match:
iex> cond do
...> 2 + 2 == 5 ->
...> "This is never true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> true ->
...> "This is always true (equivalent to else)"
...> end
"This is always true (equivalent to else)"
Finally, note cond considers any value besides nil and false to be true:
iex> cond do
...> hd([1, 2, 3]) ->
...> "1 is considered as true"
...> end
"1 is considered as true"
The idea of using the pipe was taken from F#
https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching
let printColorName (color:Color) =
match color with
| Color.Red -> printfn "Red"
| Color.Green -> printfn "Green"
| Color.Blue -> printfn "Blue"
| _ -> ()
Some ideas. Maybe two separate statements can be is
and match
.
promoting the existing keyword is
to be part of an if
.
if is
would be similar to C switch
matching only bools, strings, nums, and lists of possible values. A simpler if tablematch
would be using pattern matching for each case.:>
if is (value) { // another possible way would be if (value) is {}
| 1 : System.print("Case 1")
| 2 :> System.print("Fallthough") // to case 3
| 3 : System.print("Case 3.")
| 4, 5 : System.print("Case 4 or 5")
| true : System.print("considers any value besides null and false to be true")
} else {
// The default case
}
match (value) {
| in 1..6: System.print("Value is in range 1..6")
| in [7, 10, 12]:> System.print("Value is 7 10 or 12") // fallthough to the next case
| true: System.print("considers any value besides null and false to be true")
} else {
// default
}
Given that we don't want to add more syntax to Wren than we need to, I think that switch
(or whatever we decide to call it) needs to be a single statement and it needs to somehow combine traditional usage with simple pattern matching otherwise it's just not worth doing.
An if/else
ladder can after all deal with anything except falling through to the next clause - it's only problem is that it lacks elegance.
OK. following my previous post, I've tried to come up with a design which we might all be able to unite around by going back to traditional switch/case
syntax, with no automatic fallthrough, but with default
replaced by else
to save a keyword. Blocks would not be required for the cases
which could be one of the following:
A single value.
A list of values.
A range of values.
A type.
A boolean expression.
Option 5 would only be possible if the switch expression were a variable (no problem in practice) which would itself feature in the boolean expression. Other variables which were in scope could also be included.
Fallthrough would be catered for by a new (and spellable) fallthru
keyword which would have to be the final statement in a case
.
If break
and/or continue
were present in the code, they would refer to an enclosing loop, not to the switch
statement itself.
Here's how it would look:
switch (expr) {
case 1 :
System.print("A single value.")
case 2, 3, 5 :
System.print("One of a list of values.")
case 6..10 :
System.print("One of a range of values.")
case is Num :
System.print("The type of the expression.")
case expr <= 0 || expr == 4:
System.print("A boolean expression.")
fallthru // fall through here
else :
System.print("Default case.")
}
The code would simply iterate through the cases until it found the first match. Cases could therefore overlap each other. It's possible that the compiler might be able to implement this as an if/else
ladder though fallthrough might be tricky.
Don't use acronym, autocompletion is a thing since more than 10 years...
Also, I really don't like that each case is specific, it implies special
handling in the compiler for each specific usage, aka code explosion in
the compiler. And last case is not realistic at all. If expr
has to be
repeated, it is a simple if
.
I assume when you say: "don't use acronym" you mean don't use fallthru
- use fallthrough
and rely on auto-completion if you have trouble spelling it. I'm not going to argue with that as I don't like the former anyway.
The problem we have with option 5 is that, technically, option 1 is also a boolean expression which evaluates to true
unless it happens to be false
or null
. Unless 'expr' is repeated in option 5 the compiler may therefore have a problem in distinguishing between the two. It also enables the usage of &&
and ||
and addresses the dislike some commenters have of cases
beginning with an operator.
It seems to me that some 'code explosion' is inevitable if traditional and pattern matching approaches are to be combined in a single statement.
My solution is a little bit more elegant, you only have to trick the compiler to dupe the value on each case, and start evaluating from there. While it seems to be a lot of case, in fact there is only the general evaluation of an implicit case value expanded with the explicit expression. The rest is boiler plate to control the execution flow.
With your solution each case must be parsed and compiled differently. And this is were you solution ends in code explosion.
I don't say my code is better at all. The importance is also in the ease of writing, learning and other considerations. But my solution should have less impact with more extensibility. But this not always the way to go. ex I really don't like the new tag system, I would have preferred python decorators, and a proper type annotation, or at minimum that they follow the wren syntax to avoid the introduction of a DSL...
Misc. thoughts:
|
since Wren mostly uses words rather than symbols outside of expressions.Syntax: I prefer keywords to | since Wren mostly uses words rather than symbols outside of expressions.
Agree
"fallthrough": the Raku programming language uses "proceed"
Nicer alternative
OK. following my previous post, I've tried to come up with a design that we might all be able to unite around by going back to traditional switch/case syntax.
Considering the points made by @cxw42 I think the following syntax can be using only two new keywords: switch
and proceed
. This syntax was inspired by the @mhermier syntax.
switch (value) {
(== 42) : // implicit value
System.print("The answer")
System.print("to the universe")
proceed // fallthrough
(value.isEven): // or can be used explicit
System.print("This is odd")
(4):
System.print("How did we get there")
} else {
System.print("Default case.")
}
(By the way, thank you all for letting me join you in this discussion! I enjoy language design :) .)
I agree we are getting closer.
switch (value) { ... (value.isEven): // or can be used explicit
@clsource is value
a reserved word here? It is not currently in Wren, as far as I can tell, but rather a convention (ref). Would it need to be reserved for something like this to work?---
switch(2+2) {
(value.isEven) System.print("yes")
}
I don't think we need the colons if we are using parens --- we should be able to use Wren's regular statement forms. I like this because it is analogous to if(foo) stmt
. E.g.,
switch(value) {
(4) System.print("one-line statement")
(5) {
System.print("multi-line block")
}
}
I would rather not list the default case as a separate block, for the sake of fallthrough-to-default. For example, in C, I sometimes write:
switch(expr) {
case 1: ...; break;
case 2:...;
/* FALL THROUGH */
default: ...; break;
}
I think this would be clearer in Wren as
switch(value) {
(4) {
System.print("four")
proceed
}
(any) { // or whatever syntax we pick for the default case
System.print("default, and also 4")
}
}
than as
switch(value) {
(4) {
System.print("four")
proceed // To what? This makes me look down past two closing braces to find where control will go.
}
} else {
System.print("default, and also 4")
}
(== 42) : // implicit value
This looks like it would be tricky to parse. If nothing else, permitting (==42)
might suggest that (42==)
would be valid. I worry we would either break symmetry by forbidding the latter, or greatly complicate the expression parser.
Raku has a nice way to handle this, and I would like to suggest it here for consideration. This doesn't handle all possible cases yet, but I'd like your feedback if it might make sense for Wren, considered at a high level.
There is an binary operator called "smartmatch", spelled ~~
. It checks whether the left side matches the right side. E.g., 1 ~~1
, and also 1 ~~ 1..10
since 1
is part of the range 1..10
.
Every object has a special method that says whether a value matches that object. In Wren, I would call that method ~~(_)
, since it is what implements smartmatch. foo ~~ bar
is exactly bar.~~(foo)
.
In a switch
statement, each test expression is a full expression (which the parser can already handle). The test expression is the right hand side of a smartmatch. E.g.:
switch(2+2) {
(4) ... // Yes, matches: 2+2 ~~ 4 because 2+2 == 4, and Num.~~(_) delegates to ==(_)
("four") ... // No: !(2+2 ~~ "four")
(Num) ... // Yes: 2+2 ~~ Num because Num's metaclass has a ~~(_) that matches any number
(1..10) ... // Yes: 2+2 ~~ 1..10 because Range.~~(_) matches everything in that range
(Fn.new { |v| v<=1 || v>=5 }) ... // No: Fn.~~(_) returns the result of passing the argument to the function
(Fn.new { |v| true }) ... // Yes: a very long way of writing `default` :)
}
The parser knows how to handle the expressions, and each expression knows how to check if that case matches.
Two notes:
(4)
to use ==
instead of ~~
for speed, if it turned out that was a bottleneck.switch
without actually adding the ~~
operator to Wren, if you wanted to consider the operator separately.Let me know what you think! If you would like more detail, I would be happy to provide it, or see the linked pages. E.g., a comparison with JavaScript is here.
I don't understand the point of adding '()' or ||
around the expression. I mean:
switch(2+2) {
== 4: ...
== "four": ...
is Num: ...
in 1..10: ... // Maybe it is not a thing, but it also show it is transparent to future binary operators
~~ Regexp.new("my fancy regexp") : ...
~~ Fn.new { |v| v<=1 || v>=5 }: ...
~~ Fn.new { |v| true }: ...
}
Is clear and not ambiguous, and can work even if the author of a library didn't thought at that usage.
Maybe the point is to simulate adding case
or allow multiline without adding a block... I don't get it.
An idea, we need to discuss after this issue is resolved that I think comes from python or perl, would be to have the result of the switch to be in a meta variable like _
, so it could be used inside the switch block as if a regular switch
would be translated as:
{
var _ = (expr)
switch (_) {
is Num: System.print(_) // example of usage
}
}
If possible I would avoid _
character, since it might be needed to access method objects in the future unless we change the signature system...
A few quick comments:
"fallthrough": the Raku programming language uses "proceed"
Although it's a nice enough word, I have reservations about proceed
.
It sounds to me like it's trying to break out of the switch
statement altogether and carry on with the rest of the code rather than moving to the next case
. Also, from a backwards compatibility viewpoint, it's much more likely to have been used as an ordinary identifier than fallthrough
.
I would rather not list the default case as a separate block, for the sake of fallthrough-to-default.
Yes, I think supporting fallthrough rules out the possibility of having a separate else
block.
I don't understand the point of adding '()' or || around the expression.
Neither do I.
I've always liked @mhermier's approach here and, unless I'm missing something, I don't think it would necessarily mean that we have to use blocks for multi-line cases which I know some commenters don't like.
There is an binary operator called "smartmatch", spelled ~~. It checks whether the left side matches the right side.
That's a new concept to me - I don't know Raku at all. I'll need to think about it before commenting further.
Well smartmatch
is clearly a powerful concept from what is a terse but very expressive language (Raku).
To put something like that into Wren, I think you'd need to:
Add the smartmatch binary operator ~~
(and/or an equivalent match
method).
Provide a default implementation in the Object class, perhaps based on object equality.
Override the default implementation in each built-in class (and, where needed, in user-defined classes) as appropriate.
To use it effectively without having to look stuff up all the time, I think you'd need to try and remember how the built-in classes handle pattern matching which might be a high cognitive overhead for a simple language such as Wren.
Although I wouldn't rule this out for the future, I think for now we need to come up with something a bit simpler and more immediately intuitive.
Looking at @mhermier's latest example, I'm quite happy with how he handles the first 4 cases (though I think I might need to open a separate issue about extending the usage of the in
operator).
Stuff like this is also easy:
switch(expr) {
> 6 : ...
!= 7 : ...
.isEven : ...
in [1, 3, 5] : ... // potentially easy anyway
== true : ... // to catch anything else rather than using 'else'
}
The key point is that each case
always begins with an operator which relates back to the switch
expression.
What isn't easy with this syntax are general boolean expressions (involving && and || say) where you really need to pull in the switch variable and possibly other variables for the case
to make sense. Unless someone can think of a way to handle these, perhaps it would be best to just be content with simple patterns for now and maybe add more powerful pattern matching later.
About enforcing block, thinking at it again, I think we need to see it in action. I would prefer if we stay on the safe site, so it pairs with if
syntax. I guess it is required to avoid a confusion with expression starting with unary left operator, and simplify the compiler.
@PureFox48 I found this default
case verbose and error prone, since if the expression evaluate to false
it will not be triggered.
Yes, you're right about the default
case. I was trying to find a way which begins with an operator to be consistent with the rest of the syntax but it doesn't really work.
What would you suggest - sticking with else:
which wouldn't require a new keyword?
I think I agreed with that a while ago. Thought I wonder if user would be confused if default would happens in middle of a selection and catch all prematurely?
This can be solved by moving the else
outside of the selection block. While I don't have nothing against it, maybe it is a little bit too much confusing teaching/usage wise, in particular in interaction with if
.
Well, as @cxw42 and I said earlier, I don't think we can move else
outside of the switch
block because of the need to cater for fallthrough.
To avoid potential confusion, perhaps we could mandate that the else
case, if there is one, always comes last in the switch
.
As far as enforcing blocks is concerned, do we need one if the case ends with a colon?
This, after all, seems to be how C identifies cases without using a block though admittedly it has the case
keyword to help it. We'd just have an expression which begins with an operator (or else).
Well I think it is required because potentially, unless I'm mistaken, the grammar would become LL(N). In the context of a multiline , :
would need to be searched to determine if the expression is a case statement starting with a binary expression or a real statement starting with a unary expression.
(
will not help obviously.
|
can be working though distinguishing with bit-wise OR is potentially problematic I guess.
I thought of a simplification late last night. No smartmatch, but yes parentheses:
(foo)
tests for value == foo
{|v|...}
for everything else --- calls the function and uses the truth value of its result.No parser tweaks required because paren vs. lbrace tells you which case you are in, and no run-time overhead.
This doesn't handle literal ranges, which is a downside. It would be easy to check for ranges at runtime, but at the cost of a type check. Maybe the compiler could special-case literal ranges, but then the special case would have to handle things like (foo+2*bar)...(bat&3)
. I don't know how easy that is in the compiler.
Re. colons, I really think we should not add them if we don't absolutely have to --- @ruby0x1 said at https://github.com/wren-lang/wren/pull/952#issuecomment-815505251 that the syntax for blocks was not open for change right now (if I understood correctly). Parens and braces do the job using elements Wren already has, even if they look odd compared to other languages :) .
mandate that the else case, if there is one, always comes last in the switch.
Agreed
There is no real confusion here, the grammar interpretation of the block here change because of the keyword switch
. So it is safe to use :
here, even if it looks a lot like a map.
@cxw42 using '(' since they can start a regular expression does not solve anything. Even considering |
or [
or any other operator does not really seems to help. So unless I miss something the solution is either keyword or non ambiguous grammar to preserve LL(1) or LL(2).
I'm in favor of C-style switch
with Raku semantics.
That is,
switch (x) {
case 1..5:
System.print("x is in the range 1 to 5")
break
case "":
case "s":
System.print("Something")
break
case Num:
case String:
System.print("String or Num")
break
default:
System.print("Other")
break
}
In my experience, fallthrough is almost always not needed. If it is, you can always fall back to if
-else
. So I tend towards mandating the break
, except for adjacent case
s. If you think differently, we can allow fallthrough just like C - by omitting the break
.
Regarding requiring a default
clause, I don't think this is a good idea. Generally I do think we should test for exhaustiveness, but since we don't have pattern matching, I don't think we actually can and requiring default
will just create noise.
I can think of two improvements over C's switch
:
case
is one-line, imply the break:
switch (x) {
case 1..5: System.print("x is in the range 1 to 5")
case "":
case "s":
System.print("Something")
break
case Num:
case String:
System.print("String or Num")
break
default: System.print("Other")
}
switch (x) {
case 1..10 if (x % 2) == 0:
// ...
break
}
For the ~~
operator (or whatever it'll be named), we can implement it as the following (preferably in C when possible):
class Class { // Actually, `Class` methods can be implemented only in C, but you got the idea
~~(e) { e is this } // By default, classes matches all of their instances, so `case Num` matches all numbers
}
class Bool {
~~(e) { e == this }
}
class Num {
~~(e) { e == this }
}
class Null {
~~(e) { e == null }
}
class String {
~~(e) { e == this }
}
class Range {
~~(e) { (e is Num && contains(e)) || (e is Range && e == this) }
}
class List {
~~(e) { any {|element| element ~~ e } }
}
@mhermier I am trying to understand your points but am having trouble. Sorry about that!
I think the body of a switch can be expr, stmt, expr, stmt... , and Wren's grammar is simple enough that we only need one token of lookahead.
Could you give me a code example or something else to help me understand where we would need extra keywords or lookahead? If not, I could try writing a proof of concept, but it might take me a while :) .
@cxw42 what I mean:
switch(val) {
/* without braces */
long_check_expression: ... /* actual code follows*/
// ^ ^
// | Parsing has to go till there to decide, that long_check_expression is a check expression and
// | not a non brace expression, but it is too late to reinterpret long_check_expression
// Adding a keyword here can solve the ambiguity, this is what C and most language do
/* with braces*/
long_check_expression: ... /* a one liner*/
long_check_expression: {
... /* actual code follows*/
}
// There is no ambiguity here since there is no mix between `cases` and regular code
}
I just thought about an extra complication, handling variable declaration in this seems dirty because of fall-through. We might need to ban them.
@ChayimFriedman2 I don't like implicit ~~
as a solution for switch, while perfectly functional, it remove flexibility for the user, and make user depends on the implementor of external code.
Use can always create class Pattern
and use full pattern matching. This is just simpler IMO.
It would require to add a
switch
keyword. Butcase
anddefault
can be replaced with other available symbols like|
andelse
. It has implicit return.Other alternatives to
else
as suggested by @CrazyInfin8 :_
,default
,otherwise
. But it would probably be needed another keyword. Ifelse
is not preferred my vote would be for_
(underscore).Another way without adding a new keyword.
if
andis
Another syntax variation