28 May, 2010, Runter wrote in the 1st comment:
Votes: 0
I kinda stumbled upon this earlier and found it useful. It's not well documented in resources or the books I own for case. It has to do with the way === is implemented (which is used in case), so I suppose the info is documented for that however, in my experience, this wasn't immediately obvious when exploring the case statement.

Basically if you're using Ruby case statements you know it's a lot like switch/case statements in other languages.

i = 1

case i
when 1
puts "One"
when 2
puts "Two"
end


When you examine further it doesn't use equality to test. It uses ===. Not much of a secret there, either. Because of the way it works you can do things like this:

thing = "roar"

case thing
when String
puts "string!"
when 1
puts "One!"
end



So it matches classes as well as equality values. Interesting enough, but not all that surprising. Here's what I found surprising (because there's not much info on it) and useful.

# define a blank interface.
module Interface; end

# define 3 classes. A mixes in Interface.
# B inherits A. And C does not mix in or inherit from A.
class A; include Interface; end
class B < A; end
class C; end

arr = [A.new, B.new, C.new]

# iterate over the array and act on each of the objects.
arr.each do |iterator|
case iterator
when Interface
puts "Has interface."
else
puts "Doesn't have interface."
end
end


And here's the output for those 3 iterations.
Has interface.
Has interface.
Doesn't have interface.


It has some pretty interesting uses since you could design methods to interact with things that mix in interfaces instead of specific objects (or even classes of objects themselves). I'm currently using this for modules in my codebase such as the Living interface to make any living things equally and consistently interface driven. This certainly beats something like:

case something
when Player, Npc then "living!"
end


It would be rather lame to have to maintain code as new definitions of Living things are defined.

Obligatory cutesy example follows. (DH loves em.)
# return the correct way to examine an object passed.
def examine something
return case something
when Living then something.health_status()
when Hat then "funky #{something}"
when "Davion" then "noob"
else something
end
end

module Living
def health_status
"healthy #{self}"
end
end

# Tah cat's alive
class Cat
include Living
def to_s
"cat"
end
end

class Hat
def to_s
"hat"
end
end

class Undefined; end

#list of an assortment of stuffs.
arr = [Hat.new, "Davion", Cat.new, Undefined.new, true, false, nil]

# examine each thing in the list.
arr.each do |obj|
puts examine(obj)
end


And the output
funky hat
noob
healthy cat
#<Undefined:0x401bee88>
true
false
nil
28 May, 2010, JohnnyStarr wrote in the 2nd comment:
Votes: 0
Very neat.
I'm using modules similarly when it comes to things like Obtainable, Wearable, Inhabitable, Edible, etc.
This will be a great way to evaluate objects that may include these. Thanks for the info, I don't think
I've seen that printed either.
28 May, 2010, David Haley wrote in the 3rd comment:
Votes: 0
Runter said:
It uses ===.

Dear Lazyweb,

What are the semantics of ===?

Best regards,
- David

Runter said:
Obligatory cutesy example follows. (DH loves em.)

You should have a 'hiss' method on the cat so that I can finish my message with Cat.new.hiss. :evil:
28 May, 2010, shasarak wrote in the 4th comment:
Votes: 0
I've said it before and will no doubt say it again - switch/case statements in OO languages are (generally) evil and a sign of poor OO class design. :biggrin:

If you have a piece of code which has to go one of several ways depending on whether the object it's dealing with is living, then the question of whether or not that object is alive is something which should be determined by the object itself, not by the code dealing with it. And, in the general case, a switch statement is often a sign that you aren't using polymorphism in the way you should be.

For example, if you have a piece of code for a spaceship which makes the appropriate noise when the engine is switched on, the wrong way to do it would be like this:

switch engine.engineType
case: "hyperdrive" {write("You hear a whooshing sound");}
case: "warpdrive" {write("You hear a loud whine which steadily rises in pitch");}
case: "spacetimevortexdrive" {write("You hear a strange wheezing/groaning sound");}
case: "chemicalrocket" {write("There is a roar as the engines fire");}
default: {write("You hear the sound of the engines powering up";}
end
The correct way to do it is this:

write(engine.engineType.StartUpNoise());
and have each possible engine type class supply the appropriate noise via different polymorphic implementations of StartUpNoise().

If the code for the spaceship is determining what noise the engine makes when the engine is defined as a different class, then the division of responsibilities between those two classes is wrong.

In your "cute" example there should probably be some sort of shortDescription() method which is defined differently on different classes; so something of class Garment might return "funky " + base.shortDescription(), something living might return healthStatus() + base.shortDescription(), etc.
28 May, 2010, David Haley wrote in the 5th comment:
Votes: 0
shasarak said:
I've said it before and will no doubt say it again - switch/case statements in OO languages are (generally) evil and a sign of poor OO class design. :biggrin:

If you have a piece of code which has to go one of several ways depending on whether the object it's dealing with is living, then the question of whether or not that object is alive is something which should be determined by the object itself, not by the code dealing with it. And, in the general case, a switch statement is often a sign that you aren't using polymorphism in the way you should be.

You might want to look into object value/type pattern matching and related topics.
28 May, 2010, Runter wrote in the 6th comment:
Votes: 0
Quote
What are the semantics of ===?


It's over ridden per class to provide case specific functionality. It's default is to match against class type, equality or module included.

o = Object.new
M = Module.new

o.extend M

o.===(Object) # true
o.===(o) # true
o.===(M) #true


In a case statement

case 1
when 2 then "two!"
when Integer then "number!"
end


It's similar to

if 2.===(1) then 
"two!"
elsif Integer.===(1)
"Integer!"
end
28 May, 2010, David Haley wrote in the 7th comment:
Votes: 0
Thanks. So is it like an instance-of test? Or is it more like a several-faceted equality test?
28 May, 2010, Runter wrote in the 8th comment:
Votes: 0
David Haley said:
Thanks. So is it like an instance-of test? Or is it more like a several-faceted equality test?

Well, yeah, it tests multiple things from equality to inherited from, to extended by. But specifically for the equality part I believe it uses whatever == is defined as. Which also is overridden per class. The default == for Object (which everything inherits from) is equality at the pointer level in the core. Commonly, though, it's a higher order equality test. "a" == "a" for example. Even though two different objects the equality is true. So for === it would also be true. However, other objects that don't override == won't share that. Object.new == Object.new is false. String.new == String.new is true. Array.new == Array.new is true.

So it's a little interesting that String.===(String.new) and String.new.===(String.new) are both true, but Object.new===(Object.new) is false.

(Even though Object.===(Object.new) is true.)
28 May, 2010, David Haley wrote in the 9th comment:
Votes: 0
Quote
So it's a little interesting that String.===(String.new) and String.new.===(String.new) are both true, but Object.new===(Object.new) is false.

That makes sense, though, if the test is first "are these ==", and then "is one the class of the other".
29 May, 2010, Runter wrote in the 10th comment:
Votes: 0
Quote
In your "cute" example there should probably be some sort of shortDescription() method which is defined differently on different classes; so something of class Garment might return "funky " + base.shortDescription(), something living might return healthStatus() + base.shortDescription(), etc.


Sure, and in my example it uses to_s to do that. The general purpose ruby over-ridable method which does what you're describing. to_s is being called in those examples whether or not you realize it. The one object that doesn't override to_s there is the Undefined. And it does the default to_s functionality: print the type and memory location.

But frankly, the example wasn't for best practices—It was just exhibiting behavior and I think it probably gets the job done.

Quote
I've said it before and will no doubt say it again - switch/case statements in OO languages are (generally) evil and a sign of poor OO class design.


That's a pretty bold statement. I'm guessing your position on this is that innocuous uses of a case statement are the ones where it's high ceremony. (And aren't really useful.) And other uses are bad design. That may be legitimate depending on how you're looking at it. To be honest, it's not surprising some people may feel this way. It's why Larry Wall was so sensitive not to add it to perl, but I'm pretty sure it wasn't for OO concerns, and I'm also pretty sure it wasn't successful in keeping the language well structured. :)
29 May, 2010, quixadhal wrote in the 11th comment:
Votes: 0
LOL, perl is many things. Well structured is NOT one of them. :)

The fact of the matter is, a properly used switch/case statement is FAR easier to read and maintain than a whole slew of if/elseif/else clauses. For that reason alone, it's a useful construct. I think the folks who managed to drive "goto" into the dirt are just looking for a new construct to bully.

GOTO is also quite handy and useful, in specific and seldom-encountered situations. You'll have a hard time finding any youngsters willing to admit that though, because the OO camp branded it as Satan's own favorite statement, and brought up slideshows of BASIC code with line numbers from 1984.
29 May, 2010, Davion wrote in the 12th comment:
Votes: 0
Quote
The fact of the matter is, a properly used switch/case statement is FAR easier to read and maintain than a whole slew of if/elseif/else clauses. For that reason alone, it's a useful construct. I think the folks who managed to drive "goto" into the dirt are just looking for a new construct to bully.


Ya know, I don't think he's really talking about using an if/elif/else as apposed to a switch statement. In some cases, switch statements are used in an OO design to determine class.

switch( o->type )
{ case TYPE_PLAYER: do stuff; break;
case TYPE_OBJECT: do stuff; break;
default: break;
}


Now, this is apposed to a more OO design of

o->do_stuff();


So I don't think one time string matching (eg. matching yes/no) would really fall into a poor OO design.
29 May, 2010, flumpy wrote in the 13th comment:
Votes: 0
quixadhal said:
GOTO is also quite handy and useful, in specific and seldom-encountered situations. You'll have a hard time finding any youngsters willing to admit that though, because the OO camp branded it as Satan's own favorite statement, and brought up slideshows of BASIC code with line numbers from 1984.


Well, it's not that goto is evil per se, it"s the MISuse of it that is evil. Misuse of any programming keyword or paradigm is evil, and evilness should be stomped on.

Limiting goto is something that the language designers of Java thought was necessary, and you can experience a limited use goto int the form of break and continue keywords, both limited by compile time checks to exist only in certain code blocks.

After all, any turing complete programming language must allow JMP like functionality or well, it wouldn't be turing complete.

Switch statements aren't so cut and dry for me, any logic that requires you to create more than a three or four paths inside your method is probably too cyclomatically complex for my liking, and annoying to unit test to boot, so I stear clear of large switches and if groups if I can.

(Edit: I just noticed this is a Ruby thread, hope no one minds the cross language pollution - soz)
29 May, 2010, quixadhal wrote in the 14th comment:
Votes: 0
One of the (rare) cases I've found goto useful (in C) is to break out of a fairly nested layer of conditionals and loops, or in some cases to realize that your input is faulty but could be salvaged by jumping into a processing chunk that should have required other preceding input. You could convolute your code enough to not need goto for that, but it would make it harder to read and probably either have duplicate code, or stupidly trivial functions to avoid the duplication.

As for Davion's ob->do_stuff(), that's true if you have simple classes. There are cases where ob is expected to handle multiple types of data, and do_stuff() itself should have a switch to avoid having to have do_stuff_with_int(), do_stuff_with_string(), etc. Classes to handle database queries are a good example, since you probably don't know the column types until you actually get the result set back.
29 May, 2010, David Haley wrote in the 15th comment:
Votes: 0
flumpy said:
After all, any turing complete programming language must allow JMP like functionality or well, it wouldn't be turing complete.

Ah… no. Turing Machines are, well, Turing-complete, and there is no "jump-like" instruction. :smile:

That said, basically any control flow with if/else/while/etc. has "jump-like" functionality. There is no need for an explicit goto; it just makes things easier and more compact in some cases.
29 May, 2010, flumpy wrote in the 16th comment:
Votes: 0
David Haley said:
Ah… no. Turing Machines are, well, Turing-complete, and there is no "jump-like" instruction. :smile:

That said, basically any control flow with if/else/while/etc. has "jump-like" functionality. There is no need for an explicit goto; it just makes things easier and more compact in some cases.

Yes I recognise what you say as truth, although dubiously about the turing machine statement. I don't have my google fu to hand so perhaps you can back that up?

What I was getting at is that goto is an implicit request for a jmp, rather than implicit, and that without it a language would possibly be unusable for certain algorithms.

Ps nested quotes seem to be broken for me
29 May, 2010, David Haley wrote in the 17th comment:
Votes: 0
You posted a video that's no longer available.

EDIT:
To elaborate on what I said, the point is that Turing Machine just: reads the tape, writes the tape, and moves left or right on the tape; and changes program state depending on what it read on the tape. I guess it depends on whether you're talking about jumping on the tape or "jumping" in program state; the internal program state just shifts from one state to another. Is that a "jump"? Eh. In the sense that a change is a jump, sure. Any if statement is a jump (in fact it's often represented as jump-non-zero in assembly). Do you need conditional statements to be Turing Complete? Yes. Do you need an explicit goto? No.

You can represent a goto otherwise, anyhow, with the extreme example being prefixing every single program statement in between where you want to use the goto and where you want to go with an if statement saying "if (not_trying_to_goto)". This is a little silly, but the point is that if we're going to use words like "impossible" rather than "inconvenient", it matters.

EDIT 2: it looks like you fixed your post; I think I covered it in my above reply although I hadn't seen it.
29 May, 2010, flumpy wrote in the 18th comment:
Votes: 0
Ok inconvenient is good for me too.

Btw it seemed to be the bcove markup in the quote that messed up my post, I'll PM about it when I'm not busy irl
0.0/18