Hacker News new | past | comments | ask | show | jobs | submit login
Ruby's switch statement is flexible (akshaykhot.com)
160 points by thunderbong on April 26, 2023 | hide | past | favorite | 97 comments



`rescue` also uses the match operator, which I found useful in the past when wrapping libraries that didn't expose a useful exception type hierarchy. In particular I seem to recall that the "pg" gem would throw `PG::Error`, with the actual error details from the Postgres server only accessible in the `message`. I wrote a little library that allowed me to create "virtual" exception types that could match based on the message or the result of an arbitrary proc (if the underlying exception class offered a `code` or `reason` attribute, for instance).

For all its (sometimes deserved) reputation as a big and complex language, Ruby is actually pretty good at making sure that language features are made with the same underlying building blocks, and thus two things that look similar probably work similarly as well.


Wow I've been programming Ruby for 15 years and didn't know `rescue` uses a match operator.

It's annoying when a library raises only one exception type for every error and the only difference is in the message. Matching on the message right in the rescue expression would be tremendously useful instead of parsing the message and deciding how to deal with it inside the rescue body.


Here's the library: https://github.com/mboeh/toe_tag

It's a decade old and less than 100 lines so it might be more useful as an example than a gem. The last time I needed to do something like this I just rewrote the parts I needed. The ecosystem is just generally better about exceptions than it used to be, too.


Ruby is going through some kind of renascence age, I'm seeing allot of Ruby articles lately.


I feel like people got hung up on computational performance for a while. 5 to 10 years ago, a bunch of big startups hit the scale where they needed to intentionally work around Ruby's limitations. People seemed fixate on those performance issues while overlooking the business context - that hitting that scale means you are wildly successful and solving problems most startups never have the chance to solve.

It seems there's a swing back towards people considering raw productivity. Especially, at early stage or pre-product market fit.

Given that Rails is highly correlated with Ruby usage, it's simply hard to beat how much productivity and breadth that Rails gives you. Especially, with how much "grunt" work is easily handled by it.

I love trying new frameworks on side projects, but I always find myself coming back to Rails. There is inevitably some nuanced situation that the framework doesn't handle that would be ridiculously easy in Rails.


Ruby has also improved single-threaded performance and introduced a parallelism construct, so, while it is still far from fast, the basic performance and scaling situation has improved somewhat.


I love Ruby so much. Its syntax and flexibility are such a joy to work with. I hope it does have a resurgence in popularity. I've been using it my whole career, and fearing the day I have to switch to something new.


IMO Learning new languages is one of the best ways to be become a better programmer. You can carry the things you like about Ruby with you, and learn to think about things in new ways.


Productivity is a very sticky feature. IMO there's no language that I know of which effectively competes with Ruby in this category.

I don't even know if people realize that Ruby is the closest thing to an Aspect Oriented Programming experience in the wild, at least that I've seen. It's the secret sauce IMO.


Loving all the Ruby articles. Ruby has been my favorite language ever since i discovered it back in 08.

Before that I was using Perl..


I'm personally quite enjoying it.


Sounds like it was whether Python or Ruby is more popular back then, which was won by Python but then people still preferred Ruby because of its elegant language design and those voices seem finally heard around enough.

I even see Crystal (grammatically similar compiled language) gaining attention.

I do love Ruby syntax and used it on several occasions but it's typing system seems far from elegant compared to TypeScript in my opinion which doesn't want me to switch to Ruby at this point.


No mention of pattern matching being used with a case statement:

https://docs.ruby-lang.org/en/3.0/syntax/pattern_matching_rd...

This is where some real magic happens.


Pattern matching gets even more fun with the rightward-assignment syntax and additional conditionals. Here's a single example case from the UUID/GUID library I'm working on where the constructor method takes arbitrary positional arguments, many combinations of which may yield a valid UUID. This particular case matches the components of a Microsoft-style GUID so it can handle the endianness appropriately:

  in [::Integer => data1, ::Integer => data2, ::Integer => data3, ::Array => data4] if (
    data1.bit_length.<=(32) and data2.bit_length.<=(16) and data3.bit_length.<=(16) and (
      data4.size.eql?(8) and data4.all?(&::Integer::method(:===)) and data4.max.bit_length.<=(8)
    )
  ) then


FIY, you can write `data4.all?(Integer)` because `all?` can take a pattern instead of a block, and it uses the === (just like case...when)

I just learned this myself from another comment in this thread.

UPD: why do you write `obj.<=(16)` instead of `obj <= 16`? is this a performance thing or a matter of style or something else?


Just a style thing. I find it less visually overwhelming when every statement that contributes a true/false is visually contiguous. You can see it in context here if curious: https://github.com/okeeblow/DistorteD/blob/NEW%E2%80%85SENSA...


OP has updated it off the back of this comment.

https://www.akshaykhot.com/ruby-switch-statement#pattern-mat...


Thanks for pointing it out. I've updated it.


Tangental, but this article deserves huge credit for having code containing the main point right at the top allowing the reader to derive the main point in < 20 seconds. I wish more written content was like this. Brilliant.


It doesn’t actually list all interesting cases discussed in the article, including the casein pattern matching at the end.


That's likely because the pattern matching usage was a late addition to the article (wasn't planned from the start)


It’s also missing string-literal matching, relational operators (<), and (more interestingly) type matching.


And loses credit for a title making click bait claims about what I think or don’t think


Ruby is a wonderful language, I just wish I wasn't stuck with 2.6 at work because all of the improvements especially in Ruby 3.0 are just wonderful. I keep wanting to branch out to other languages but keep coming back to Ruby as my bread and butter.


May I recommend to anyone facing similar issues and who may have at least some agency in dealing with the problem (can't assume you do, so forgive me in that case) the incredible work of Victor Shepelev with Ruby References: https://rubyreferences.github.io/rubychanges/evolution.html

The site presents evolutions of Ruby since version 2.0 in an editorialized and well-written categorized release journal called "Ruby Evolution": https://rubyreferences.github.io/rubychanges/evolution.html

There's also individual version releases annotated as well, for example for the recent Ruby 3.2: https://rubyreferences.github.io/rubychanges/3.2.html

Note that these are not copies of the NEWS.md typically released when minor and major versions of Ruby come out. Victor specifically spent time to write more descriptive notes of what each notable change occurred over time. It's an incredible resource and we're extremely lucky to have him in our community.

There's even a changelog for this meta-changelog, which makes my little Keep a Changelog heart sing, so you can see evolutions of this site over time as well: https://rubyreferences.github.io/rubychanges/


I moved us up from 2.4 to 2.7 last year trivially. Basically just changing the version base in our dockerfile. A few gems had to go up too, to support the kwargs changes. But like... 1 day of work. I'm looking to go to 3.x soon too, after I get our EOL'd rails & lots of gems updated.

What features in particular are you looking at in Ruby 3.0? The type system stuff seems really cool, but I haven't been hearing much about it in the wild.


Unfortunately, the switch from 2.7 to 3.0 is likely to be more troublesome, due mostly to the change in handling keyword arguments (they're no longer treated as equivalent to a last positional argument that happens to be a hash, and you need to toss in '*' to convert from one convention to the other). This isn't hard to clean up if you have good test coverage... but one reason for not having done upgrades is not having that.


This is a convention we've already adopted but we haven't had the chance to do a clean sweep of the rest of the codebase unfortunately. You're right though, it's one of our biggest hurdles to upgrading.


You really need to push to upgrade to 3.0. Ruby 2.6 and 2.7 have passed EOL and are no longer being supported.


Believe me, I know. But management doesn't give a crap about that. We're still on Postgres 10 as well as Redis 5. Send help.


1. Ask management to write in full letters and sign a paper basically saying "I acknowledge that not maintaining software up-to-date is a security risk and opens the company to substantial financial damage"

2. Hire a Pwn-As-A-Service to extract data from your company from the outside

3. Tell management "I told you so"

You didn't read this here

More seriously, I think the best you can do is not expect to find happiness in a setting that you don't fully control (ie your job), accept that, and look for joy elsewhere. It might be another job, it might be another hobby. Work fewer hours.


I appreciate your advice. I regularly voice concerns about security and let management make decisions. From my perspective I've done my due diligence and my hands are clean.

I've stopped looking for "joy" from work. Mainly focused on finding joy with my children and other hobbies, while working as little as possible. The job is the means to that end now, rather than the end itself. They continue to pay me and it supports the things I want to do outside of work so I am "happy" enough. The work itself is also moderately interesting and a decent challenge from a domain perspective, so it's not all bad.


> I appreciate your advice. I regularly voice concerns about security and let management make decisions. From my perspective I've done my due diligence and my hands are clean.

I feel like the best you can do unironically is the first step I outlined: ask management to formally recognize that despite your warnings, they choose cost reduction. Have a trace of that, not just in case of problems, but for yourself: it's a great reminder that as long as you aren't in charge, it can't be your problem.


> More seriously, I think the best you can do is not expect to find happiness in a setting that you don't fully control (ie your job), accept that, and look for joy elsewhere. It might be another job, it might be another hobby. Work fewer hours.

That's how I upgraded from Ruby 2.4 / Rails 5.0 to Ruby 3.2 / Rails 7.0


Oh in that case, there's actually a very simple way to upgrade: get a job somewhere else.


Woof. Postgres 11+ also has some nice speed improvements with each version as well.


I do some...let's say non-trivial reporting and there's so many new features in modern postgres that would make my life so much easier, whenever I accidentally end up on a documentation page for a newer version with something I want but can't use I die a little inside.


There is ruby-next gem for polyfills :)

https://rubygems.org/gems/ruby-next-core


very cool. Thanks


If you're willing to share, what's holding your work back at 2.6.

Afaik the upgrade has been reasonably smooth, and nothing like the ruby 1.9 days.


We're on a 2.x version as well.

For us, it's just incredibly hard for us to justify the effort _and_ risk. Particularly, the risk.

Most of us have been burned by a major upgrade (either here or elsewhere) where things have broken in non-obvious, hard to test ways. This is particularly problematic in the dependency chain where support is limited. Breaking issues may turn us into unexpected maintainers of key libraries.


> If you're willing to share, what's holding your work back at 2.6.

Management.


I'm not saying that it's the case for you, but a lot of time I've seen that this actually means "engineers aren't able/willing to talk with management to justify the value of maintainance and make a concrete proposal of how to go about it".

But then again, some other times management just sucks.


Naah, most of the times its that management doesn't want to pay the migration cost. They will keep using a system till it stops producing value, or till adding new things is profitable. They can't give a damn about day to day or what's cool/elegant/maintainable because they don't interact with the innards, ever. These decisions are made with a single equation, if migration cost > new feature cost + maintenance, they don't want to migrate.

Edit: The maintenance isn't a long term view btw. They are not looking at a 5-6 year window where the migration cost slowly pays off. They are worried about optimizing the next quarter.


We upgraded from Rails 4 to 5 last year (lol - it was EOL before our migration was even done), but that was before our CTO left to be a CEO somewhere else and there's been no attempt to find a replacement. Now the executive is obsessed with piling on features with little regard to technical debt or maintenance. It's going about as well as you'd think.


I’ve been lucky to have been working in smaller companies where I’ve been able to communicate the issues with that.

I can’t imagine working in a place that doesn’t want to listen to its engineers or experts. (The same works the other way around too: engineers not willing to understand the business context)


It also likely means no one in management understands building software.

It's tiring when every line of code needs a value proposition and a clear use case. Some things don't take much work to improve/maintain but are next to impossible to slap a $ value on.


Have you considered replacing them with ChatGPT?


I think if someone can replace their management with chatGPT, they can probably just do the migration :P


    even = ->(x) { x % 2 == 0 }

    even === 4  # true
    even === 5  # false

That's cool. No need for a is_even or is_odd gem.

I really do like that ability to do a case statement though. I cannot think of a situation where I need it now but I could definitely see it being handy instead of having to do if..else if


Alternatively you can call Integer#even? or Integer#odd? directly:

    4.even? # true
    5.even? # false
Or if you really want to use ===, it is possible with the slightly sillier (imho) use of Symbol#to_proc:

    case some_num
    when :zero?.to_proc then "zero"
    when :even?.to_proc then "even"
    when :odd?.to_proc then "odd"
    end
I say sillier, because even though it is very explicit, the use of #to_proc, by name, feels just a tiny bit less awesome than its usual use via the & operator, e.g. array_of_nums.map(&:odd?). It would be really cool to be able to write:

    case some_num
    when &:zero? then "zero"
    when &:even? then "even"
    when &:odd? then "odd"
    end
However I say this with no grasp of just how complex that could be to implement, or other implications for the language's grammar.


Small mistake on the `/\D/`: the correct semantic is "the string (or any matchable class) includes a character that is not a number".

It will also never match a number, although it's kinda ok because it's supposed to be an example of the functionality.


Am I too dumb to find it or do the Ruby docs not contain a description of the language?

https://ruby-doc.org/3.2.2/ only seems to contain API docs.


This is not an official documentation site.

The only official Ruby documentation exists at https://docs.ruby-lang.org/en/ and is generated via the Ruby source code itself.

The excellent Colby Swandale has been working on a new open source alternative for Ruby documentation considering the design shortcomings of the default generated API documentation with https://rubyapi.org which I highly recommend.

If you're coming at Ruby from another language, the official Ruby site offers https://www.ruby-lang.org/en/documentation/ruby-from-other-l... and other documentation aside from API concerns here: https://www.ruby-lang.org/en/documentation/

Hope that helps.


It’s actually linked from the official site: https://www.ruby-lang.org/en/documentation/


I feel fooled, I've always thought https://ruby-doc.org was the official website!


Sadly there are a lot of third-party operated documentation sites that exist likely because of how hard-to-discover the official API documentation site is. It's something the community would like to address, but given the amount of link equity a lot of these older and often unmaintained doc sites have acquired it's fairly tricky.


That's the Core reference guide. You might want the book "Programming Ruby"[1], which is linked on the documentation landing page[2]. Alternatively, for a quick overview, I recommend Learn X in Y Minutes[3].

[1]: http://ruby-doc.com/docs/ProgrammingRuby/

[2]: https://www.ruby-lang.org/en/documentation/

[3]: https://learnxinyminutes.com/docs/ruby/


From [1] > This book documents Version 1.6 of Ruby

Has nothing about the language changed since 1.6?


Hah yeah, uh, make sure you get a more recent edition that was published less than 20 years ago. A profound amount has changed since then.

Looks like the 5th edition covers v3.2.


The Ruby docs are, generally, the weak spot of the language (moreso than even being slow, on which there is also more progress).

Fortunately (while there was a long time where this was not the case), there is a reasonably current version of the “Pickaxe” book, which is the closest existing thing to the documentation Ruby should have: https://pragprog.com/titles/ruby5/programming-ruby-3-2-5th-e...


You need to take a closer look at the Pages sidebard, there is for example this https://ruby-doc.org/3.2.2/syntax/pattern_matching_rdoc.html


Did you expect something else? The way I get to that link is to go to ruby-doc.org and click on "Complete API docs for Ruby 3.2.2". API docs is what I expected.


The rules for one of my favorite games says that it can only be experienced, not explained. I see that Ruby has the same philosophy.


Yeah, ruby is a weird one. They have excellent documentation of the API, but all the language documentation is wildly out of date. The main web site links to an old version of "Programming Ruby" which covers 1.6. Has nothing changed about the language in 20 years?


I find ruby-doc.org very poorly designed.

There is an unofficial website with a better design, https://ruby-docs.org


The Ruby docs are incredibly difficult to navigate.


Ruby does not have a switch statement; it has a case expression (which can be used in a statement).


That naming is rather maddening.


It’s the same in SQL and Ada. “Case” is an abbreviation for “case distinction” here.

“Switch” was never intuitive to me, because nothing gets switched.


"Switch" to this statement in "case" of this expression, seems quite easy to grasp.


It's the same in Haskell, Erlang and Elixir.


The "C" family has one of the worst case/switch statements out there. When Java et. al. cloned it, they should have provided an alternative. Perhaps keep the old style for backward compatibility, but provide a new one with different key-words to avoid confusion. Example:

       select(a) {
         when 1,2,3 {...}
         when 4 {...}
         when 5,6 {...}
         otherwise {...}
       }


Agreed that the C-style switch is crammed with misfeatures. But Java does now let you write this (haven't tried compiling this, may contain small errors):

    static String describe(int change) {
        return switch (change) {
            case 0                  -> "unchanged";
            case -1, 1              -> "mostly unchanged";
            case Integer i && i > 0 -> "increased";
            case Integer i          -> "decreased";
        }
    }
No fallthrough, can mix constants and conditions (with syntax some will find clunky), and is an expression.


It's cool that Ruby has a === operator, but the fact that it is not a more strict version of == has caused me trouble in the past.

In TypeScript === is more strict, and when I go back and forth between Ruby and TS I have made a mistake in the past by putting a === when I intended a strict equality check.

I wonder if there is a linting setting where you can prevent your code base from ever using it.



Very similar to Ada'a switch statement, which utilities "case .. when". I don't think Ada can utilize regex in their switch statements but they can do neat things like convert a string into its corresponding enumeration.


Whoa that’s wild.

> Note: There're no break statements at the end of each when clause. Unlike other languages, Ruby's case doesn't fall through.

Aw no fall through?

> Note: Do not use obj.class in the case clause, as Integer === Integer returns false.

Wait what


The switch statement in Ruby uses the `===` method under the hood. `===` does not mean "equals" like it does in Javascript. Instead, `a === b` means, does `b` belong in the set of `a`.

    irb(main):001:0> Integer === Integer
    => false
    irb(main):002:0> String === String
    => false
    irb(main):003:0> Object === Integer
    => true
    irb(main):004:0> Object === String
    => true


Yeah: it's almost more true to say that `a === b` just means whatever we've decided `case b when a` should check. `===` is almost never used directly.


This makes more sense and seems less egregious when you look at it in use, in a case:

  a = []
  case a
  when Array
    puts true
  else
    puts false
  end


This might confuse a reader. The Object === cases are successful, because the Class-objects Integer and String are Objects. In ruby everything is an Object.


That's a good point. This might be a better example:

    irb(main):009:0> String === String
    => false
    irb(main):010:0> Integer === Integer
    => false
    irb(main):011:0> Class === Integer
    => true
    irb(main):012:0> Class === String
    => true


> Wait what

Ruby's triple equals (`===`) is defined differently based on the type of the operand on the left hand side.

`a === b` is syntactic sugar for `a.===(b)`; and for classes `klass.===(other)` is defined as `other.is_a?(klass)`.

More examples here: https://dev.to/baweaver/understanding-ruby-triple-equals-2p9...


> Aw no fall through?

That's a good thing. In fact, that's the only thing on the article that is unambiguously good.

Fall through only serves to create bugs.


naw, fallthrough is fun, and helps you be explicit about handling cases without repeating yourself.


> Wait what

`Integer ===` (and more generally if `===` is implemented as a class method) checks if the right hand side is of the class Integer. The object Integer is not, it's an object of class Class.

EDIT: In other words:

    $ pry
    [1] pry(main)> Integer === Integer
    => false
    [2] pry(main)> Class === Integer
    => true
    [3] pry(main)> Integer.class
    => Class


This seems to work instead of fall through, though:

when 60, 70


That's because it's more like a compacted if/else ladder than what I'd traditionally think of a switch as (indexed jump).

SQL has CASE...WHEN which is a similar concept.


Sorta? Like you're right, there's not compiled machine code that does a jump. But Ruby's case statement has nice semantics to make clear code out of what could be a complex if/elsif/else chain. The use of the === function is powerful, and lets the reader of the code focus on the condition, not the function call to check it.

    if (patternA.match(input))
    elsif (patternB.match(input))
    else
    end
vs

    case input
    when patternA
    when patternB
    else
    end
And since it uses ===, you can define that on your own classes too. Hypothetically for instance, you can make fairly complex policy type classes to make reusable boolean statements.

    case current_user
    when NotValidatedEmail
    when NotCompletedOnboarding
    when SomethingElse
    else
    end
and then you can use those elsewhere in the code by leveraging the more global use of ===

    [user1, user2].any?(NotCompletedOnboarding)


> there's not compiled machine code that does a jump

It's more of an implementation detail, but the Ruby VM actually does a jump if all the cases are "keyable"(typically integers, strings, symbols, etc). It does a hash lookup and jump to the returning address.

https://github.com/ruby/ruby/blob/92466e440d459cd21e89f8bfbe...


That's cool, thanks for the code pointer. I knew I was probably wrong in the strictest sense, especially with all the work that's gone into the various approaches to JIT and performance work in Ruby.

I should go read some of the interpreter code at some point - fun work going on there.


Swift: Hold my beer


Standard ML enters the bar.


Raku would like to have a word with you.


In what sense does it offer in pattern matching that ML can't do?


This made me laugh:

def can_drive(age) case age when 1..14 then 'no' when 15..100 then 'yes' end end

puts can_drive(18) # yes

I haven't used Ruby for a long time, and I don't feel bad about it.




Consider applying for YC's Fall 2025 batch! Applications are open till Aug 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: