case
The case gem is a power-up for Ruby’s case-matching.
Structs
Case::Struct
is a specialization of Ruby’s standard Struct
class which supports structural matching. When two instances of a normal Ruby Struct
are matched, their fields are compared using the normal equality test. Case::Struct
uses the case-matching ===
operator over each field instead.
Here’s an example of some of the things which are possible:
require 'case'
Foo = Case::Struct.new :foo
[Foo["blah"], Foo["hoge"], Foo[3], Foo["testing"]].each do |value|
case value
when Foo["testing"]
puts "matched testing"
when Foo["blah"]
puts "matched blah"
when Foo[String]
puts "matched string #{value.foo}"
else
puts "fell through"
end
end
This will print:
matched blah
matched string hoge
fell through
matched testing
Wildcards
The case gem also supports wildcards. For example:
require 'case'
Foo = Case::Struct :name, :arg
[Foo["blah", 9], Foo["meat", 5], Foo["blah", 23]].each do |value|
case value
when Foo["blah", Case::Any]
puts "blah: #{value.arg}"
end
end
This will print:
blah: 9
blah: 23
Case::Any
can also be used directly, though there’s seldom reason to do so instead of using an else
clause.
case thing
when "foo"
# specific case
when Case::Any
# fall-through
end
case thing
when "foo"
# specific case
else
# fall-through
end
Conjunction
Case also supports simultaneously testing several cases in a single when:
[4.5, 3, 3.0].each do |value|
case value
when Case::All[Float, 3]
puts "float 3"
when 3
puts "generic 3"
when Float
puts "generic float"
end
end
This prints:
generic float
generic 3
float 3
Disjunction
It’s also possible to match one of several alternatives, if you pass arguments to Case::Any
:
require 'rational'
require 'case'
[3, Rational(3, 1), 6.0, 3.0].each do |value|
case value
when Case::All[Case::Any[Float, Rational], 3]
puts "matched #{value.inspect}"
end
end
Which prints:
matched Rational(3, 1)
matched 3.0
Once again, Case::Any
can be used on its own, though usually there’s not much point.
case thing
when Case::Any["foo", "bar"]
# either of these
end
case thing
when "foo", "bar"
# either of these
end
(Note that Case::Any[]
with no arguments matches nothing; it is different from Case::Any
without braces.)
Comparisons
Case::Cmp
can be used to perform comparisons as part of case-matching:
require 'case'
(0..5).each do |value|
case value
when 0, 1
puts "0 or 1"
when Case::Cmp < 4
puts "less than 4"
else
puts "fell through"
end
end
This prints:
0 or 1
0 or 1
less than 4
less than 4
fell through
fell through
The <, >, >= and <= operators are all supported.
Guards
More general tests can be put in when statements using Case.guard
.
require 'case'
["foobar", "beef", "lorem"].each do |value|
case value
when Case.guard { |v| v[1, 1] == "o" }
puts "second letter in #{value} is o"
else
puts "just got #{value}"
end
end
This will print:
second letter in foobar is o
just got beef
second letter in lorem is o
(Yes, I realize a regular expression could be used for this purpose.)
In simple cases, when Case.guard
is being used directly, you don’t even need to bother with the block parameter:
["foobar", "beef", "lorem"].each do |value|
case value
when Case.guard { value[1, 1] == "o" }
puts "second letter in #{value} is o"
else
puts "just got #{value}"
end
end
(The parameter is really only there for when Case.guard
is being used as part of a larger expression.)
Negation
Case::Not
inverts the normal matching. For example:
[1, "foobar", :blah].each do |value|
case value
when Case::Not[String]
puts value.inspect
end
end
This will print:
1
:blah
Arrays
Case::Array
is a subclass of Array
which can be used as a pattern to do positional matching in arrays:
require 'case'
[["foo", 2], ["bar", 3, 4], ["bar", 9]].each do |value|
case value
when Case::Array["foo", Case::Any]
puts "Foo #{value[1]}"
when Case::Array["bar", Case::Any]
puts "Bar #{value[1]}"
end
end
This will print:
Foo 2
Bar 9
Note that Case::Array
only matches arrays of the same length.
Case::Array
should only be used as a pattern; there’s no need to use it in lieu of Array
for your actual data.