Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Implicit switch if function has single parameter and consists of when clauses #2490

Closed
gaearon opened this Issue · 38 comments
@gaearon

Hi, I'm sorry if this has been proposed and/or rejected before.

In Nemerle and maybe some other languages, you can omit switch statement if function has just one parameter and contains no statements other than when clauses.

In this case, the compiler will assume you want to switch on the first parameter.

    @actions.on 'click', (action) ->
        switch action
            when "undo" then @undo()
            when "redo" then @redo()
            when "compose" then @compose()

Could be re-written as this:

    @actions.on 'click', (action) ->
        when "undo" then @undo()
        when "redo" then @redo()
        when "compose" then @compose()

with the compiler assuming you're switching on action.

Can this kind of syntax sugar make its way into CoffeeScript?

@michaelficarra
Collaborator

Cool. I'd rather adopt the LiveScript behaviour of switching on truthiness than switch on the first param. It can be applied to all functions, not just 1-parameter functions.

@alpha123

While this is neat, at least that example would be better written as

    @actions.on 'click', (action) ->
        if action in ['undo', 'redo', 'compose']
            @[action]()

I +0 on this. I think most use cases would be like the above, but it still has some utility.

@davidchambers

In Nemerle and maybe some other languages, you can omit switch statement if function has just one parameter and contains no statements other than when clauses.

This strikes me as awfully narrow. Additionally, I don't find the switch line overly noisy. Finally, it's odd to be forced to name a parameter which isn't referenced explicitly.

-1

@alpha123

Finally, it's odd to be forced to name a parameter which isn't referenced explicitly.

I'd consider this a non-issue, as we would probably switch on arguments[0], so

@actions.on 'click', ->
    when "undo" ...

could compile to

this.actions.on("click", function () {
    switch (arguments[0]) {
        case "undo": ...
    }
});

you can omit switch statement if function has just one parameter

I see no need for this to apply to functions with just one parameter. No reason we couldn't have

calculate = (op, operand1, operand2) ->
    when "plus" then operand1 + operand2
    when "minus" then operand1 - operand2

Which of course is highly contrived, but you can see the point.

@jashkenas
Owner

Cool. I'd rather adopt the LiveScript behaviour of switching on truthiness than switch on the first param.

Ditto. That sounds like a neat way to go.

@vendethiel
Collaborator
@jashkenas
Owner

Merging in a patch for @satyr's "guard-style if" form. Should address this nice and clearly:

if
  condition
    action()
  otherThing is true
    otherAction()
  else
    defaultAction()

Or, more compactly:

if
  options.parse then parse()
  options.run then run()
  options.repl then repl()
@jashkenas jashkenas closed this in 13fae12
@michaelficarra
Collaborator

@jashkenas: I find that really weird. So now blocks can follow any expression? Everywhere else a block starts, something on that line at least indicates what it's doing. And I don't see what this new syntax gets us over condition-less switches.

@jashkenas
Owner

So now blocks can follow any expression?

Nope. Just in this particular case of the expression being the condition. It's like:

if condition
  action

... but with the if out an additional level:

if
  condition
    action
  condition
    action

I don't see what this new syntax gets us over condition-less switches.

Clarity. They're not switches, they're ifs. switch should really be matching against a particular value.

But thanks for the feedback -- anyone else have stylistic objections? It's certainly not too late to kill these.

@satyr
Collaborator

Woot, quick adoption.

My only gripe for this syntax is it doesn't look good on its own due to the lone if and an additional indent. Best used with ->, = etc.

@isaksky

This is great. It is the same as Clojure's cond:

http://clojuredocs.org/clojure_core/clojure.core/cond

@joehannes

thumbsup <3

maybe you could come up with something like

?: cond
?: other
!: else

and why stop there ... at times I feel the need for a "finally" in a switch ... you know, that's always when you write this switch to see ... ahh, that's basically the same functionality in all the conditions, I should sum that up in a centralized foo. OK, but then, it's just that tick different you really are spending time on pure elegance for ...?? what exactly, yes, your ego or self-justification of poor looking code... fill in good reasons...
That's when I'd like to kind of "init" those vars in the implicit if and execute the summary in the finnaly declaration!

Also ... you might want to provide an injector for the implicit if ...
say you leave it like that and say something like

if raw_switchvar
of foo:expectedVal
of otherFoo:expectedVal

the scenario is similar to the previous with the finally idea... but slightly different.

okay!!! cheers, good job!

@derryl

What's wrong with this?

if condition
    ...
or condition
    ...
or condition
    ...
else
    ...

Or this?

if condition
    ...
elif condition
    ...
else
    ...

The newline just kinda bugs me. Doesn't seem like the cleanest option available.

I like simply using or / else if / elif, and keeping the usage more consistent with a regular if statement.

That said, whatever format you roll with – this is certainly a welcome feature :)

@michaelficarra
Collaborator

I just don't get why we'd want to drop the when/else if. It's the opposite of this proposal, which was asking to drop the head (switch). What we have now doesn't get us any closer to guards.

@jashkenas
Owner

Because we want the language to work out. when is currently meaningful as part of a switch statement, which should operate on values. if (or unless) is the bit of language that we use to evaluate a truthy condition. We don't want to muddle the two.

I can imagine an alternate past where we never used if, and only ever had when:

when condition
  action
when predicate value
  otherAction
else
  defaultAction

... but that's not the precedent, I'm afraid.

@michaelficarra
Collaborator

But what does it buy us over regular if/else if/else? Do we have an example that's made more readable by this extra, seemingly unnecessary, indentation?

@maccman

Yeah not a fan :(

@satyr
Collaborator

what does it buy us over regular if/else

One less else if to type for each extra condition, e.g.:

->
  if a
    b
  else if c
    d
  else if e
    f
  else
    g

becomes:

-> if
  a
    b
  c
    d
  e
    f
  else
    g

What does your preferred (LiveScript's) "implicit switch" buy us over? Only a switch:

-> switch
  when a
    b
  else
    c

->
  when a
    b
  else
    c

Moreover, we now could ditch topicless switch in favor of this form of if which compiles far more cleanly.

@jashkenas
Owner

Moreover, we now could ditch topicless switch in favor of this form of if which compiles far more cleanly.

I'd love to do that. Do you think it would break a lot of folks, or do you think topicless switches are rare beasts that haven't been used by many?

@vendethiel
Collaborator

I'd say it's a common use, but github search is basically unusable at that point. No way to search topicless switch and at that point, the 8 first pages are 2 snippets repeating over and over.

@JacobOscarson

I've used topicless switches a bit to accieve things similar to the discussion above but would be happy to replace them with a more compact syntax.

@opensas

I'm not too convinced about the extra indentation. Maybe something like this:

if condition
  action()
otherThing is true
  otherAction()
else
  defaultAction()
@balupton

Seems a bit strange that you can have lines (the cases) that don't appear to do anything special unless you have the context of the previous lines (the if). This issue is avoided with switch/when as each line has the necessary context.

It makes me wonder why people couldn't just do the following if they wanted such abilities.

switch true
    when a
        b
    when c
        d
    when e
        f
    default
        g

But either way, I'm okay with it, it doesn't seem like it will cause regressions, just doubt I would ever this over the more explicit/readable options prior to this.

@vendethiel
Collaborator

It makes me wonder why people couldn't just do the following if they wanted such abilities.

The true itself is not needed. And to answer :

what does it buy us over regular if/else

One less else if to type for each extra condition, e.g.:

@epidemian

Not a fan of the multi-condition if. I think one of the advantages (probably the biggest one) of being able to drop the switch when switching over true is to avoid having that extra indentation level. This new if form doesn't have that advantage.

Because we want the language to work out. when is currently meaningful as part of a switch statement, which should operate on values. if (or unless) is the bit of language that we use to evaluate a truthy condition. We don't want to muddle the two.

That's a fair point. But i personally see the switch as a multi-condition statement, a shorthand for chained else ifs. So, to me, it is this new if form the one that muddles the two constructs.

@satyr
Collaborator

I'm not too convinced about the extra indentation. Maybe something like this:

if condition
  action()
otherThing is true
  otherAction()

Wouldn't work due to ambiguities as in:

if a
  b
c # call? else-if?
  d: e

The extra indent is beneficial when used as guards, assigned, or called.

-> if
 a then b
 c then d

r = if
  a then b
  c then d

f if
  a then b
  c then d

read better than:

-> if a
  b
else if c
  d

r = if a
  b
else if c
  d

f if a
  b
else if c
  d
@michaelficarra
Collaborator

@satyr:

One less else if to type for each extra condition, e.g.:

Sure, at the expense of a little less readability due to more parsing context. Also, an additional syntactic feature with an additional indentation level, as opposed to this proposal, which gives the option for one less indentation level.

What does your preferred (LiveScript's) "implicit switch" buy us over? Only a switch:

One less level of indentation when it's unnecessary. Putting the switch on the same line as the function was abusing the fact that we have single-line functions whose contents can span multiple lines. Omitting it entirely would be ideal.

Moreover, we now could ditch topicless switch in favor of this form of if which compiles far more cleanly.

Moot point. When they have the same semantics, we can compile them to the same thing. It's not even difficult to detect those cases.

@davidchambers

One issue I have with this addition is that else appears to be nested inside the if block:

if
  .........
    ........
  .......... .. ....
    .............
  else
    ................

I find the switch true approach clearer than the above, though this may simply be familiarity at play.

@michaelficarra
Collaborator

@davidchambers: That example with everything unimportant masked off as dots is very compelling. Brilliant idea.

@satyr
Collaborator

else appears to be nested inside the if block

It sure does. On the first draft this syntax had no else involved, just like Arc's if:

if
  a then b
  c # else

which didn't line up well and would likely to end up cond style: true then c. So I added else.

@jashkenas
Owner

Shall we adopt this same treatment for switch as well, for parallelism?

@jashkenas
Owner

Alright.

I had a working branch going with the same treatment for switches -- you could drop all of the when from the inner indented block. I'm going to revert the changes here, tempting as they are, because of the generally lukewarm response, and because I think that the readability of this syntax, while extremely tempting for short and sweet examples, really begins to fall down for any complex examples. Let's take a look -- this is lovely:

if
  opts.nodejs      then forkNode
  opts.help        then usage
  opts.version     then version
  opts.stdio       then compileStdio
  opts.eval        then compileScript null, sources[0]
  opts.interactive then require('./repl').start()
  else runScripts

But this is pretty terrible. It would be easy to forget entirely what you're looking at if you jump in right in the middle:

if 
  value is '=' and prev
    if not prev[1].reserved and prev[1] in JS_FORBIDDEN
      @error "reserved word \"#{@value()}\" can't be assigned"
    if prev[1] in ['||', '&&']
      prev[0] = 'COMPOUND_ASSIGN'
      prev[1] += '='
      return value.length
  value is ';'
    @seenFor = no
    tag = 'TERMINATOR'
  value in MATH            then tag = 'MATH'
  value in COMPARE         then tag = 'COMPARE'
  value in COMPOUND_ASSIGN then tag = 'COMPOUND_ASSIGN'
  value in UNARY           then tag = 'UNARY'
  value in SHIFT           then tag = 'SHIFT'
  value in LOGIC or value is '?' and prev?.spaced then tag = 'LOGIC'
  prev and not prev.spaced
    if value is '(' and prev[0] in CALLABLE
      prev[0] = 'FUNC_EXIST' if prev[0] is '?'
      tag = 'CALL_START'
    else if value is '[' and prev[0] in INDEXABLE
      tag = 'INDEX_START'
      switch prev[0]
        when '?'  then prev[0] = 'INDEX_SOAK'
@jashkenas jashkenas referenced this issue from a commit
@jashkenas Reverting #2490 dbb99f3
@vendethiel
Collaborator
switch a
  in b
    foo bar
  [...a]
    do baz

?

( @jashkenas and @satyr )

@satyr
Collaborator

Woot, quick rejection. Nice troll.

@archaeron

What is really nice about the proposed syntax is that it is easy for the eye to spot the conditions because they are all on the same line. I like it.

@michaelficarra
Collaborator

@jashkenas: Has the original proposal been rejected as well? I'm still for adding that or an equivalent.

@jashkenas
Owner

Yes, I think so. We shouldn't be encouraging folks to write topic-less switches. if is the appropriate keyword there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.