March 3, 2015 by Daniel P. Clark

Ruby: The Case for Case

It’s nice to have case and when available in Ruby.  But I don’t see it used too often.  I myself don’t use it much because I’m more accustomed to just using if and else.  So let’s get into case switching.

person = "employee"

case person
  when "boss"
    puts "Yes sir!"
  when "employee"
    puts "Sure I'll get around to that if I have any time."
  else
    puts "*smile and nod*"
end
# => "Sure I'll get around to that if I have any time."

This is your standard use of  case switching.  We check whatever variable is handed to case against each when condition.  If they match then that section of code is executed and the rest won’t be evaluated.

Alternatively you can just not hand a value to case and use when like you would with if and else.

dollars = 10_000_000

case
  when dollars > 999_999
    puts "You're a millionaire!"
  when dollars == 0
    puts "You're flat broke!"
  else
    puts "You are not flat broke, nor are you a millionaire."
end
# => "You're a millionaire!"

Those are the simplest usages for case.  But wait!  It gets better.  You can evaluate the Object handed to case with a Proc in your when clause.

Proc it!

hour = 14

case hour
  when ->(a) { a.between?(14,15) }
    puts "It's tea time."
  else
    puts "Go on with your life."
end
# => "It's tea time."

So what happens if we do a method call as the case parameter?  Lets use Time.now .  I’ll use Procs with print statements to show whether we’re getting changing information.

case Time.now
  when ->(time){ puts time; sleep(1); false}
    puts "nothing"
  when ->(time){ puts time; sleep(1); false}
    puts "nothing"
  when ->(time){ puts time; sleep(1); false}
    puts "nothing"
  else
    puts "The else result was called!"
end
# 2015-03-03 12:22:10 -0500
# 2015-03-03 12:22:10 -0500
# 2015-03-03 12:22:10 -0500
# The else result was called!

So from this we see that the method handed to case is executed and evaluated to a hidden internal variable from which to compare.  So it acts just like a variable for something handed to case.  Why do I say hidden?  Because there is no way to evaluate the case item inside the when block; if it’s not defined outside of case.  See here:

case Time.now
  when ->(x){true}
    ->(y){puts y}.call
end
# ArgumentError: wrong number of arguments (0 for 1)
#         from (irb):79:in `block in irb_binding'
#         from (irb):79:in `call'

Here we see our inner proc with y isn’t being given any arguments which is raising an error for us.  So the when clause will get handed the object to evaluate, but the code within the when block doesn’t get to see it.  Of course this isn’t an issue if you simply assign a variable before the case statement; then it’s available throughout.

Regex

You may use regex to evaluate your case variable.

case "asdf"
  when /^[A-Z]*$/
    puts "upper case"
  when /^[a-z]*$/
    puts "lower case"
  else
    puts "aaah!"
end
# => "lower case"

Perhaps evaluating whether a string is upper case or lower case would be the most contextually appropriate use for case ;-).  Just kidding of course.  There are methods for that.

Caution, Weirdness, Don’t Do This

Now it is possible to change the object during the when evaluation.  This can lead to none of the cases ever evaluating as true.

case "a"
  when ->(n){puts "'#{n.next}' == 'c'"; n.next!.equal?("c")}
    puts "nothing"
  when ->(n){puts "'#{n.next}' == 'b'"; n.next!.equal?("b")}
    puts "nothing"
  when ->(n){puts "'#{n.next}' == 'a'"; n.next!.equal?("a")}
    puts "nothing"
  else
    puts "The state failed to match up."
end
# 'b' == 'c'
# 'c' == 'b'
# 'd' == 'a'
# The state failed to match up.

For this same reason it’s probably not a great idea to use a Proc as a case switch with something that changes like Time.now .

case ->{Time.now}
  when ->(time){ puts time.call; sleep(1); false}
    puts "nothing"
  when ->(time){ puts time.call; sleep(1); false}
    puts "nothing"
  when ->(time){ puts time.call; sleep(1); false}
    puts "nothing"
  else
    puts "The else result was called!"
end
# 2015-03-03 12:24:29 -0500
# 2015-03-03 12:24:30 -0500
# 2015-03-03 12:24:31 -0500
# The else result was called!

So you can see that the Proc calls a fresh result from Time.now in each when case and the time has changed between each.  So in this case you may never hit the when clause even though one of the when clauses may be true at one point during the evaluation.  Is this likely?  Probably not.  But it is possible.

You can case case statements.  A case statement returns a result just like anything else in Ruby and you can check another case against it.  Is this useful?  I don’t know.  There may be an excellent use case for it somewhere in the future.  But the more I think about it the more I think it’s not advisable.

case case case
      when true == true
        true
    end
    when ->(x) { return x }
      puts "true has been returned from the inner case"
  end
  when ->(x) { x.nil? }
    puts "the print statement returned nil"
end
# true has been returned from the inner case
# the print statement returned nil

Additional Notes

I’ve touched on self recursive (tail recursive) methods once or twice.  Aja Hammerly uses case statements for some of her conference talk demonstrations.  It was from her conference talk up on Youtube from which I learned that you can call case without passing it something to evaluate.

In my mind it’s mostly preference that leads to using if and else over case.  But at times it may be more elegant to use case.  What do you think?  What’s the best use case for case?  Also do you know of any other behaviors or oddities with case?  I’d really like to hear about it!

Hope this was both enlightening and enjoyable for you!  Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by Tom Godber via the Creative Commons Attribution-ShareAlike 2.0 Generic License.

#case#else#if#lambda#proc#ruby#switch#switching#when

Comments

  1. Bob Nadler Jr.
    March 3, 2015 - 7:32 pm

    Nice post! I tend to stay away from writing long case or if/else statements, but I learned some interesting properties about the case statement reading this.

    • Daniel P. Clark
      March 3, 2015 - 8:50 pm

      I’m glad you’ve found this useful for you.

  2. Kevin Thompson
    March 4, 2015 - 12:41 am

    Great article! There might actually be a use case for passing methods or procs that contain complex logic. The one thing that came to mind that you didn’t cover was when statements accepting multiple values.

    
    between_zero_and_two = -> (x) { x.between?(0, 2) }
    equal_to_one = -> (x) { x == 1 }
    
    case 1
    when 3, 4, 5
      p 'More than two, less than six.'
    when between_zero_and_two, equal_to_one
      p 'One'
    else
      p 'Something else'
    end
    
    • Daniel P. Clark
      March 4, 2015 - 1:16 am

      Awesome! Thanks for the tip! I wasn’t aware of it.

    • Robert Fletcher
      March 12, 2015 - 7:45 pm

      You can use ranges for this as well:

      
      case 1
      when (3..5)
        p 'More than two, less than six.'
      when (1...2), 1
       p 'One'
      else
        p 'Something else'
      end
      
      
  3. crazymykl
    March 4, 2015 - 8:41 am

    Firstly, nice article. This is a good introduction to the case statement, although it’s missing a very common use case. Case statements are often used to switch on t he type of the argument, which I didn’t see an example of here.

    Also, it would’ve been nice to mention that case works on all these predicates by invoking #=== on them, so you can customize the behavior of your own classes in case statements.

    Finally, proc and lambda are not interchangable in Ruby, procs do not get their own stack frame (so return statements will take you out of the containing function as well), and do not enforce their arity. When you write ->, you are declaring a lambda.

    • Daniel P. Clark
      March 4, 2015 - 10:11 am

      Thanks!

      I’m very interested in seeing how that common use type is implemented; “switch on the type of argument”. Could you provide some sample code for that?

      I had seen it mentioned about the triple equals evaluation. I hadn’t heard it explained in quite that way. Is that the exclusive way the case/when checks things? Is that calling .call() on procs/lambdas by evaluating the #=== method on them?

      I was aware about ->{ } syntax being lambdas. Correct me if I’m wrong, but as I’ve been told: All lambdas are procs, not all procs are lambdas, and what’s commonly referred to as a stabby proc is a proc that behaves like a lambda. If that’s correct then wouldn’t be okay to call it a proc? Since lambdas are in essence a subset of proc? Or am I doing a disservice by not saying lambda for stabby procs?

      Thank you for sharing those facts. Of those facts it is lambda that I am familiar with as well as it’s behavior. Forgive me for any negligence on my part, I’m an autodidact when it comes to programming. I glean what I can from many different resources, and learn most from practising what I learn. Where I end up not quite as knowledgeable often comes down to technicalities and my terminology isn’t always accurate.

      • Keith Bennett
        March 4, 2015 - 11:51 pm

        Dan –

        Nice article. I didn’t know that case could be used without a case variable. I like the idea of using case when’s instead of elsif’s. For some reason it seems more readable to me.

        I’ve never heard of stabby proc, but I have heard of stabby lambdas; but as I understand it, the ‘stabby’ refers only to the ‘->’ notation and has nothing to do with the object produced.

        Regarding procs and lambdas, it is unfortunate that in Ruby it’s fuzzy. Perhaps the best way to think of it as that a Proc can be either a proc (note the lower case) or a lambda; in other words, all lambdas and procs are instances of Proc. The way to find out which it is at runtime is to call the Proc instance method #lambda?:


        2.1.2 :004 > ->(){}.class
        => Proc
        2.1.2 :005 > ->(){}.lambda?
        => true
        2.1.2 :007 > Proc.new {}.class
        => Proc
        2.1.2 :008 > Proc.new {}.lambda?
        => false

        • Daniel P. Clark
          March 5, 2015 - 9:59 pm

          Hey Keith,

          I’ve never heard of stabby lambda. Jim Weirich called them stabby procs as well as other well known keynote speakers. You can hear Jim Weirich call it a stabby proc at about 15 minutes and 48 seconds in on his “Y Not- Adventures in Functional Programming” talk. So this, and other sources like it, is where I learned about stabby proc.

          Do you think I’d be alright with referring to lambdas with the upper case Proc? Is that considered the category identifier for both procs and lambdas? Since lambdas are a kind of proc I’d like to be able to call it by it’s kind and let the code clarify the context. It seems to be a nitpick issue for people on the web.

          • Keith Bennett
            March 5, 2015 - 11:22 pm

            Hi, Dan. I didn’t know that, but you’re right, he did call them stabby…Procs? They are Procs but not procs….that is, the code I posted above proves that they are lambdas and not procs. Given that Procs and procs are indistinguishable when spoken, I usually say lambda and non lambda procs when I need to differentiate them.

            Since lambdas and non lambda procs behave differently, I think it’s important to be precise. If what you’re saying applies to both, of course, it doesn’t matter, and Proc is fine in writing; when speaking I will sometimes say ‘lambdas and non lambda procs’ or ‘Proc instances’ to differentiate it from ‘procs’.

            In the wider world of functional programming, I believe lambda is in wider use, so I wouldn’t shy away from it just because a lambda is a Proc.

  4. Wilson Silva
    March 5, 2015 - 4:32 pm

    I like your writings. I wish you had a newsletter.

    • Daniel P. Clark
      March 5, 2015 - 9:44 pm

      Thanks! What is it you’re looking for in a newsletter? Blog posts delivered to your e-mail address?

  5. Robert Fletcher
    March 12, 2015 - 7:37 pm

    A fun alternative to your dollars example, though not necessarily better:

    
    case dollars
    
    when (1_000_000..Float::INFINITY)
    # alternatively: `when (1_000_000..(1.0/0.0))`
    
      puts "You're a millionaire!"
    when 0
      puts "You're flat broke!"
    else
      puts "You are not flat broke, nor are you a millionaire."
    end
    
    • Daniel P. Clark
      March 13, 2015 - 11:33 am

      Thanks! Yeah I’ve been trying to think of place to use Float::INFINITY myself so this definitely fits the bill! 😉

Leave a Reply

Your email address will not be published / Required fields are marked *