
Using ‘switch’ to match arbitrary logic instead of string or number literals - tosh
https://twitter.com/swyx/status/1163225169676132353
======
skrebbel
Coded the "normal" way, you get this:

    
    
        if(githubUrl) {
            return config.github;
        }
        if(twitterUrl) {
            return config.twitter;
        }
        return {
            to: '',
            ...
        };
    

IMO this is both clearer and easier on the eyes. Can anyone show me an example
of this `switch` pattern that actually makes it better than the obvious if-
based version?

I mean, given that they're early returns, you don't need any `else`
statements. And in situations where you couldn't use early returns, the
`switch` pattern would require `break;` statements. That would make that code
uglier to the same extent as adding `else`s above. What am I missing?

~~~
tumetab1
In the end I think it's a option of style in which "if" and "switch" are
neither the correct match.

The goal is to define a value (a variable or return value) in which a value is
always established and it's established through several checks (exclusive or
not).

In this case the goal is to the definitively define a config given a URL.

The "switch" and a default condition which I think express better that a value
must be defined. With "ifs" we can add more complex logic and avoid the
problematic "breaks" but we loose that in your face "default" condition.

In theory it would be better to have a different control structure like
"pickJustOne":

    
    
      return pickJustOne(url) {
        githubUrl: config.github;
        twitterUrl: config.twitter;
        default: config.unknown;
      }
    

"pickJustOne" would have a companion named "pickOneByOrder" that would work
like an "if" where the order of evaluation would matter.

~~~
Razengan
EDIT: Oops, chalk this up as another case of reinventing the wheel. We already
have all this in the form of the ?: ternary operator, as masklinn pointed out:

[https://news.ycombinator.com/item?id=20736714](https://news.ycombinator.com/item?id=20736714)

\----

Inspired by this, I cooked up a crude imitation in (ugly/functional'ish?)
Swift:

    
    
        let result = [(firstCheck,  1),
                      (secondCheck, 2),
                      (thirdCheck,  3)]
                      .first { $0.0() }?.1
    

That iterates over an array of (() - > Bool, Value) tuples and returns the
second element of the first tuple whose first element is a function that
returns true, otherwise nil. The types of all values must be the same. Could
shorten .first to .firstTrue or something.

And a version that's closer to your idea:

    
    
        typealias BooleanFunction = () -> Bool
        typealias FunctionValuePair<ResultType> = (BooleanFunction, result: ResultType)
    
        func pickOne<ResultType> (_ pairs: FunctionValuePair<ResultType>...) -> ResultType? {
            pairs.first { function, result in
                function() == true
            }?.result
        }
    
        let result = pickOne(
            (firstCheck,  "one"),
            (secondCheck, "two"),
            (thirdCheck,  "three"))
            ?? "default"
    

You could make the default result an argument for pickOne so that it never
returns nil, but I prefer to keep it an optional.

Though it's too early in my day to think of better names. :)

------
dep_b
I do use patterns like this (Swift code):

    
    
       switch (githubURL, twitterURL) {
       case (let .some(url), _): return config.github // I prefer GitHub and ignore Twitter 
       case (.none, let .some(url)): return config.twitter // Otherwise I'll see if I have Twitter
       case (.none, .none): return {} // If I have neither I'll create something new
       }
    

But the code looks like JS and I think it doesn't support switching on tuples?

Bonus in Swift is that the compiler actually deduces that I handled all
possible cases. No surprises here. Much better than if/then

~~~
bobbylarrybobby
This would probably get messy if you had a large number of URLs to check, and
also would be very hard to modify. A bunch of `if` statements that return
early would be a lot easier to work with.

~~~
dep_b
And a bunch of `if` statements could get hard to maintain once it turns into a
bunch of related compound statements, while it doesn't check for missing
combinations compile-time.

But I don't think you should call any function outside of constructors with
more than three-ish parameters to begin with, so probably you would get a
dictionary or list of URL's by the time it grows out of control.

------
nyir
Yeah that's been around for some time
[http://clhs.lisp.se/Body/m_cond.htm](http://clhs.lisp.se/Body/m_cond.htm)

------
drbojingle
IMO, I don't really like it as a switch or the if statements. My issue being
the multiple booleans being passed to a single function. I could see it
growing to 3, 4, or greater too if more social options were added. I prefer
enums.

    
    
       SocialUrl = 'github' // or twitter or w/e
    
       return socialConfig[SocialUrl] || defaultSocialConfig
    

or how about:

    
    
       const getConfig = (key, default) => config[key] || default
    

Logic per key value isn't necessary.

edit: disregard. Misunderstood the code =)

~~~
masklinn
There are no multiple booleans passed to the function, there are actual github
and twitter URLs being passed in, and likely integrated into the config
objects before being checked for existence.

~~~
drbojingle
ahhhh ok, then I've misunderstood the code, thank you. Disregard :)

------
pikzel
In Scala you can do this pretty neatly:

    
    
      url match {
       case GitHubUrl => config.github
       case TwitterUrl => config.twitter
       case _ => defaultUrl
      }

~~~
dep_b
Kotlin and F# have the same, such a clean pattern.

------
majewsky
In Golang, a missing switch expression is equivalent to the value `true`, so
this shortens further to:

    
    
      switch {
        ...
      }

~~~
krylon
But in Go, case-values must be constants, right? This would make the trick
from the referenced source code impossible, I think. (Whether or not that is a
good or a bad thing is a question I am deliberately ignoring.)

~~~
lsaferite
Not true. Go uses logic expressions in the case statements all the time. And
as pointed out above, a switch defaults to checking against true.

[https://tour.golang.org/flowcontrol/11](https://tour.golang.org/flowcontrol/11)

------
neilwilson
It's a fairly common pattern. Essentially it's a slightly cleaner way of doing
a multiway conditional rather than if and else.

Ruby has it explicitly (case, when, else), and of course functional languages
with pattern matching operate like this as a matter of course. A Boolean guard
followed by the code to run if the guard is matched.

------
UweSchmidt
That's a nice source of bugs, if you are too quick to assume what the logic
does, or what's being returned from elsewhere and just put it into the switch.
I found it helpful to devote another line of code and explicitly handle the
outcome of computations or return values (except of course in trivial cases).

------
Traster
Apparently you would see this commonly in hardware design back in the day
because one of the compilers picked this up and mapped it to a look up table
in the right way where other expressions might not be picked up correctly. I
could easily see the same trick working in a software compiler too.

~~~
yvdriess
JS's switch is a different beast. The OP's statement doesn't work in C/C++
because its case labels need to be unique.

You also would need a large number of case statements before a modern
optimizing compiler will decide a switch statement is worth a jump table
rather than conditionals.

~~~
archi42
In fact, the compiler building a static switch table from non-constant
expressions sounds a little bit impossible ;-)

------
siempreb
The fact that it works doesn't mean it's good code. A switch statement should
not be used like that IMAO because every developer just like the author is
confused by first sight. Readability is an extremely important part of good
code.

Personally I would prefer somthing like this:

    
    
       return (githubUrl)
          ?   configGithub
          :   (twitterUrl)
             ?   configTwitter
             :   configDefault;

~~~
lsaferite
I personally find this _way_ less readable at a glance.

Every argument you make against using switch statements I would make against
doing this as well.

When you consider the semantics of the statements, using switch statements for
this makes more sense than chaining ternary statements. Chained ternary
statements is just a less readable if/else chain. Personally, if one of my
guys chained a ternary like this I would immediately fail the PR.

IMHO at least.

------
aiCeivi9
So if both conditions are true is switch using earlier option? Or is the order
undefined? I prefer to `switch` over mutually exclusive options.

~~~
sli
The order is defined. If you supply an object with both keys, you always get
the Github return value.

------
based2
[https://en.wikipedia.org/wiki/Pattern_matching](https://en.wikipedia.org/wiki/Pattern_matching)

[https://alvinalexander.com/scala/how-to-use-if-then-
expressi...](https://alvinalexander.com/scala/how-to-use-if-then-expressions-
guards-in-case-statements-scala)

------
jasode
fyi... recent related (but not a dupe) discussion:
[https://news.ycombinator.com/item?id=20066628](https://news.ycombinator.com/item?id=20066628)

------
bruce_one
Coffescript uses this technique:
[https://coffeescript.org/#switch](https://coffeescript.org/#switch)

------
zwetan
good case for replace switch with polymorphism

------
aasasd
This usage is very annoying. It can match on completely arbitrary conditions
while pretending that they're homogeneous like conditions in ‘switch’ are
supposed to be. You wouldn't fire up a ‘switch ... case ...’ to check one
condition, which is what you do in the “early return” pattern that's a lot
better for disparate checks than switches and elses.

It also uses syntax that's different from regular C-style “I (command you) {to
do stuff}”.

All that for very dubious benefit, as in I can't see any. I knew exactly one
dude who insisted on writing this way, and he's also the only one I know who
supported Putin, so I guess pick your choice.

~~~
Razengan
That correlation is hilarious.

~~~
aasasd
The guy also liked Microsoft's ecosystem (despite coding for a Linux backend)
and drove a Chinese-made SUV. I'm comfortable writing off the `switch` pattern
as another one of his idiosyncrasies. It also annoyed everyone who came across
his code, just as his political views irked people outside of keyboard-mashing
time.

