-3

If I write this:

case v when String puts :String else puts :other end 

If I set v to "some string" I get 'String'.

If I set v to String, I get 'other'.

How am I supposed to 'switch' on a variable containing one of several class objects?

How does this honour the well-established computing principle of 'least surprise'?

Please don't tell me to monkey-patch the 'Class' class.

5
  • Possible duplicate of Ruby class types and case statements Commented Apr 3, 2017 at 13:50
  • 2
    The "least surprise principle" doesn't say anything about your least surprise. Commented Apr 3, 2017 at 14:00
  • 3
    monkey-patch the 'Class' class Commented Apr 3, 2017 at 14:17
  • The case statement is very surprised to see an instance of Class. Commented Apr 3, 2017 at 14:47
  • monkey-patch the Module class. Commented Apr 3, 2017 at 15:44

4 Answers 4

3

I agree that === (the test used by case) can be confusing.

It's often called "case equality operator", but === is neither reflexive nor symmetric nor transitive, so it doesn't behave like an equality at all :

String === "test" # true "test" === String # false String === String # false String === Class # false Class === String # true Class === Class # true 

The best description I've read for a === b is from @JörgWMittag's answer :

"If I have a drawer labelled a would it make sense to put b in that drawer?"

For your problem, you could write :

v = String case v when Class puts "#{v} is a Class, let's investigate some more" # another case, if statements or hash lookup... else puts :other end 
Sign up to request clarification or add additional context in comments.

5 Comments

Class === Class #⇒ true is worth to appear in the list :)
"If I have a drawer labelled String would it make sense to put String in that drawer?" – still sounds reasonable.
If I have a drawer labelled Drawer would it make sense to put Drawer in that drawer?
The problem is the labelling. It's not just a (statically) but a's definition of of ===. So when String would become “If I have a drawer labelled ‘instances of String’ would it make sense to put String in that drawer?”
@Stefan: You're right, this definition doesn't always work. Still, it's hard to find something better. In String === String case, eiko's answer is a better fit : "Does String describe String? #=> false".
1

How am I supposed to 'switch' on a variable containing one of several class objects?

With Module#<=:

case v when String then "instance of string" when ->(c) { c <= String } then "class derived from String" end 

How does this honour the well-established computing principle of 'least surprise'?

Perfectly.

Comments

1

You can use case without an object:

case when v == String puts 'v is String class' when v.is_a?(String) puts 'v is an instance of String' else puts 'v is something else' end 

This resembles an if-elsif-else expression.

2 Comments

v.is_a?(String) does exactly what triple-equal does, doesn’t it? I understood the question as there is a need to check whether v is a String (instance of Class, String, or a subclass of String.)
@mudasobwa sorry, my bad.
1

Case expressions in ruby and some other functional languages are quite different from their imperative switch statement cousins.

As others have mentioned, case expressions use the === method, which is a pattern-defining method. In the a === b paradigm, a is a pattern which describes a collection of possible instances. b is an instance which potentially fits into that pattern / collection. Think of a === b as:

does 'a' describe 'b'?

or

does 'a' contain 'b'?

Once you understand this, String === String #=> false is not so surprising because String is an instance of Class so it fits into the Class pattern.


The other unique distinction between case expressions and switch statements is that case expressions are just that: expressions. Which means you can do cool stuff like this:

puts case v.name when "String" :String else :other end if v.is_a? Class 

This code executes only if v is a class, then switches on v.name and puts whatever you want, based on the name of the class.

Because of ruby's nature as a duck-typed language, it's exceedingly rare to have a class in a variable and need to switch on it. If you're frustrated that case isn't as elegant as you had hoped, give us more context about your goal and we might be able to come up with an elegant solution which avoids switch all together.

1 Comment

Good answer. I lke does 'a' describe 'b' as a loose definition for ===. It probably doesn't always work, but it might cover cases not covered by "If I have a drawer labelled a would it make sense to put b in that drawer?"

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.