Case statement in ruby
10 Feb 2016Case
In other languages also know an switch
statement, ruby uses case
to match a value
against conditions:
something = rand(10)
result = case something
when 1 then 'one'
when 2 then 'two'
else 'other'
end
puts result
Unlike other languages ruby has some neat features that allow you to test against ranges, multiple values or classes as well:
something = ["a string", 1, Date.today, 100, 200].sample
result = case something
when String then 'found the class'
when 0..10 then 'was in the range'
when 100 then 'is an integer'
when 200, 300 then 'multi values'
else 'other'
end
puts result
You can even use it without passing a value. I think this is extremely ugly but decide for yourself:
result = case
when Date.today.wday == 0 then 'Sleep'
when Date.today.wday == 6 then 'Party'
else 'Work'
end
puts result
Well, no. Don’t decide for yourself. It is ugly. And useless given that we have if/else
Case equality
So how does this work? How does ruby know when to match? Enter the case equality operator:
===
Everything that responds to ===
can be used in the when clause:
class MatchAll
def ===(other)
true
end
end
something = ["a string", 1, Date.today, 100, 200].sample
result = case something
when MatchAll.new then 'match all'
else 'other'
end
puts result # => match all
Sugar
As you might know writing
foo === bar
is just syntactic sugar for
foo.===(bar)
What Ruby does is to call the ===
method on the object you passed to when
, supplying the value you passed to case
as the single argument.
Procs and Lambdas
In 1.9 ===
was added to Proc
as a way to invoke it. This allows it to be the target of a when
clause in case
statement:
even = -> (value) { value.even? }
odd = -> (value) { value.odd? }
something = rand(100)
result = case something
when even then 'even'
when odd then 'odd'
else 'what?'
end
puts result
Strange world
When you use a Proc
but do not supply a value to case
then strangely ruby just evaluates the first when
clause:
callable = -> (value) { puts "was called"; true } # never called
result = case
when callable then 'when'
else 'else'
end
puts result # => will always print "when"
I’d have expected an exception.
But you came to the conclusion that a case
statement without a value is ugly anyway, right?
Performance
What about performance of a case
statement compared to if/else
?
Well, if you don’t use Procs
then the performance is the same. But since calling Procs
is expensive it might be worth rewriting a case
to if/else
:
require 'benchmark/ips'
one = -> (value) { value == 1 }
two = -> (value) { value == 2 }
thing = 1
Benchmark.ips do |x|
x.report('case with inlined lambdas') do
result = case thing
when -> (value) { value == 1 } then 'one'
when -> (value) { value == 2 } then 'two'
else 'something else'
end
end
x.report('case with lambdas') do
result = case thing
when one then 'one'
when two then 'two'
else 'something else'
end
end
x.report('if/else') do
result = if thing == 1
'one'
elsif thing == 2
'two'
else
'something else'
end
end
end
Running above benchmark should show you something like:
Calculating -------------------------------------
case with inlined lambdas
60.612k i/100ms
case with lambdas 96.626k i/100ms
if/else 117.181k i/100ms
-------------------------------------------------
case with inlined lambdas
1.203M (± 5.9%) i/s - 6.001M
case with lambdas 3.259M (± 6.0%) i/s - 16.233M
if/else 5.182M (±11.8%) i/s - 25.545M
Of course the runtime of the code depends on the value of thing
. Adjust it so that first, second or third case is matched to see the differences.
But in any case: using Procs
is slower than the corresponding if/else
.