4

Ruby's case statement uses === by default. Is there a way to make it use 'equals to' (i.e. ==) instead?

The motivation for doing so is because I have 5 if statements that would very nicely be replaced by a switch, but I was a little surprised to learn that

datatype = "string".class
if datatype == String
puts "This will print"
end

is not the same as

case datatype
when String
puts "This will NOT print"
end
stevec
  • 41,291
  • 27
  • 223
  • 311
  • [ref](https://stackoverflow.com/a/629722/10522579) can you help for this. – ray Jan 31 '19 at 12:24
  • Ruby's case statement uses `===`. It doesn't "use it **by default**". – sawa Jan 31 '19 at 12:28
  • I came across [this](https://stackoverflow.com/a/5694333/5783745) (looks like behaviour of `case` with respect to classes has tripped up some others too – stevec Jan 31 '19 at 13:05

4 Answers4

4

You cannot let case to not use ===, but you can redefine === to use ==.

class Class
  alias === ==
end

case datatype
when String
  puts "This will print"
end
# >> This will print

Alternatively, you can create a specific class for doing that.

class MetaClass
  def initialize klass
    @klass = klass
  end

  def ===instance
    instance == @klass
  end
end

def meta klass
  MetaClass.new(klass)
end

case datatype
when meta(String)
  puts "This will print"
end
# >> This will print
sawa
  • 165,429
  • 45
  • 277
  • 381
  • Is it dangerous to redefine `===` ? Could it be used in other methods (that I may not be aware of) and is it possible I might ruin disrupt other methods by redefining it? – stevec Jan 31 '19 at 12:21
  • `===` is rarely used other than in `case` statements, so it is not that dangerous. But if you have concerns with it interacting with other parts of the code, then consider changing the aliasing into a method definition within a Refinement and use the relevant `case` statement within the scope of the Refinement. – sawa Jan 31 '19 at 12:23
  • This is kind of same given [here](https://stackoverflow.com/a/629722/10522579) but nicely put here +1 – ray Jan 31 '19 at 12:46
3

This would be a more concise and cleaner (IMSO) approach to what @sawa had suggested. Use λ instead of the wrapper class.

META = ->(type, receiver) { receiver == type }

case "string".class
when META.curry[Integer] then puts 'integer'  
when META.curry[String] then puts 'string'  
end  

#⇒ "string"

This solution uses Proc#curry and Proc#=== under the hood.

Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
2

Similar to Aleksei Matiushkin's answer, but without the curry:

is_class = ->(klass) { ->(item) { item == klass } }

10.times do
  case ['abc', 123].sample.class
  when is_class[String]
    puts "it's the abc"
  when is_class[Integer]
    puts "easy as 123"
  end
end

What happens here?

  • The first line defines a proc that returns another proc
  • proc[x] is the same as proc.call(x)
  • proc.===(x) is the same as proc.call(x)
  • is_class[Integer] returns a proc that does { |val| val == Integer }
  • ..which gets called by case with the case's parameter as argument because of === ==> call.

The big downside is it creates a lot of procs and looks weird.

Of course the obvious solution to your question would be to not do datatype = "string".class:

datatype = "string"
case datatype
when String
puts "This will print"
end
Kimmo Lehto
  • 5,910
  • 1
  • 23
  • 32
1

You could also use the explicit form of case statement

datatype = test_object.class

case
when datatype == String
  puts "It's a string"
when datatype < Numeric
  puts "It's a number"
end

Note that the expression datatype < Numeric will be true for all numeric types.

Peter Camilleri
  • 1,882
  • 15
  • 17