Classes in case statements - ruby

I'm unable to determine why these two functions behave differently. I could just use symbols or my own constants, but I have a deep desire to know what's going on here (and if I'm doing something bad).
def convert(value, type)
case type
when Integer
value.to_i
when String
value.to_s
else
value
end
end
def convert_with_if(value, type)
if (type == Integer)
value.to_i
elsif (type == String)
value.to_s
else
value
end
end
n = 4.4
p convert(n, Integer) #=> 4.4
p convert_with_if(n, Integer) #=> 4

case calls ===, the case equality operator. Module#===, and by extension Class#===, actually tests if the given argument's class is the receiver or is one of its descendants. String === object is practically equivalent to object.kind_of? String.
convert 1, String
Would be equivalent to:
case String
when Integer
1.to_i
when String
1.to_s
end
That's like asking is the String class an Integer?, or is the String class a String?. The answer to both questions is No, it is a Class..
In terms of code, String.class returns Class, which is not related in any way to Integer or even String itself. If you introduced a when Class or when Module clause, it would be executed every time.

In your convert function you are getting the type Integer which is Class. That's why it's different from what you expect.

The convert method is not matching anything and defaulting to the else which is why you get 4.4.
Also, if you expect the method to output 4.4 then you would need to use decimal and not integer.

Related

Ruby: Why is my method non-destructive?

I'm trying to create two methods, one of which will destructively add "i" to any string, and one of which will do the same non-destructively.
def add_i(string)
new_string = string + "i"
end
def add_i!(string)
string = string + "i"
end
My questions are:
Both of these methods are non-destructive, even though I do not replace the argument in the second method with a new variable. Why?
In general, how do I convert a non-destructive method into a destructive one and vice versa?
The answer lies in the scope of the vars and the behavior of the methods/operators. the left hand side (left to the = )string inside add_i! is a different string than the one passed in (the right side string and the method arg). The old string continues to live on but the string var points to the new one.
to make the 2nd method "destructive" you could do something like:
def add!(string)
string << "i"
end
as a rule of the thumb, you need to understand if the methods/operators you are applying are operating on the data itself or are returning a copy of the data (for example the '+' upstairs returns a copy)
an easy way of dealing with string and making sure you don't destroy the data is to use dup() on whatever is passed in and after that operate on the copy.
The problem is + returns a copy of the string. I guess this is analgous to adding numbers which will return two numbers. string += 'i' also makes a copy. This a bit surprising, but it is the same as what numbers do.
You can see this by checking the object_id at each point.
def add_i!(string)
puts "string passed into add_i! #{string.object_id}"
string = string + "i"
puts "string in add_i after + i #{string.object_id}"
end
foo = "blah"
puts "string about to be passed into add_i! #{foo.object_id}"
add_i!(foo)
puts "string after calling add_i! #{foo.object_id}"
string about to be passed into add_i! 70364940039240
string passed into add_i! 70364940039240
string in add_i after + i 70364940039020
string after calling add_i! 70364940039240
Note that string in add_i after + i has a different object id.
<< and concat both append to the existing string. concat should probably be concat! but it isn't.

Ruby type conversion arbitrary by needed class

Example. I have two variables with random classes:
first = 12345 #Fixnum
second = "12345" #String
Can i convert second var to class identical of first variable?
i can do it with if block:
if first.class == Fixnum
second = second.to_i
elsif first.class == String
# do nothing
end
but, can a do it simple, instead if or case constructions?
You can use a case statement.
result = case first
when Fixnum
second.to_i
when Array
[second]
else
second
end
However, if you start to have several values, you may want to consider a better design pattern. For example, you can wrap second in custom object types that properly implement a casting technique.
result = first.class.cast(second)

Turning an argument into a receiver

I created the following extension
class String
def is_a_number? s # check if string is either an INT or a FLOAT (12, 12.2, 12.23 would return true)
s.to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) == nil ? false : true
end
end
How can I make it work as a chained method?
is_a_number?("10") # returns true
"10".is_a_number? # returns an error (missing arguments)
Update
Thanks sawa, mikej and Ramon for their answers. As suggested, I changed the class to Object and got rid of the argument (s):
class Object
def is_a_number? # check if string is either an INT or a FLOAT (12, 12.2, 12.23 would return true)
to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/) != nil
end
end
It now works perfectly fine:
23.23.is_a_number? # > true
Thanks guys...
When you write "10".is_a_number?, you already have the object "10" you want to check for, which is the receiver of is_a_number?, so your method doesn't need to take any parameters.
Because match is an instance method on String, you don't need to specify a receiver for it. It will just operate on the same object on which is_a_number? was called. Because you know you already have a String object, the to_s isn't needed either.
Just write it as:
class String
# check if string is either an INT or a FLOAT (12, 12.2, 12.23 would return true)
def is_a_number?
match(/\A[+-]?\d+?(\.\d+)?\Z/) != nil
end
end
Ramon's suggestion that you may want to put your extension on Object rather than on String is a good point if you don't know if the object you're testing is going to be a string.
Also, what you're describing isn't really what is meant by method chaining; it's just calling a method on an object. Method chaining is where the return types of methods are set up so that several methods can be called in sequence e.g in Rails, something like
User.where(:name => 'Mike').limit(3) # find the first 3 Mikes
is an example of method chaining.
It seems like you want to patch Object instead of String (since you are calling to_s):
class Object
def is_a_number?
to_s.match(/\A[+-]?\d+?(\.\d+)?\Z/).nil?
end
end
You could also look at replacing with it with validates numericality: true on your model.

ruby and references. Working with fixnums

I know a bit about ruby way to handle objects and references. The replace stuff, ect ...
I know it d'ont work on fixnum, cause the var is the fixnum. But i wish to change the value of a fixnum inside a function, and that the value changed in the ouside var.
How can i do this ?
I guess i can use a string like this "1" but that's quite dirty.
Ruby will always pass-by-reference (because everything is an object) but Fixnum lacks any methods that allow you to mutate the value. See "void foo(int &x) -> Ruby? Passing integers by reference?" for more details.
You can either return a value that you then assign to your variable, like so:
a = 5
def do_something(value)
return 1 #this could be more complicated and depend on the value passed in
end
a = do_something(a)
or you could wrap your value in an object such as a Hash and have it updated that way.
a = {:value => 5}
def do_something(dict)
dict[:value] = 1
end
do_something(a) #now a[:value] is 1 outside the function
Hope this helps.
You could pass an array with a single number, like [1], or a hash like {value: 1}. Less ugly than a string, as your number itself remains a number, but less overhead than a new class...
When I was building a game I had the same problem you have. There was a numeric score that represented how many zombies you've killed and I needed to manually keep it in sync between Player (that incremented the score), ScoreBar and ScoreScreen (that displayed the score). The solution I've found was creating a separate class for the score that will wrap the value and mutate it:
class Score
def initialize(value = 0)
#value = value
end
def increment
#value += 1
end
def to_i
#value
end
def to_s
#value.to_s
end
end

How can I make a custom Ruby type behave like a string?

If I have a custom Ruby class representing some string type, as in
class MyString
end
Which functions should I implement in order to make the following use cases possible:
Passing a Ruby string whenever a MyString is expected
Passing a MyString whenever a Ruby string is expected
Comparing a Ruby string with a MyString value (it shouldn't matter whether I use s == t or t == s).
I saw various interesting functions like to_s, cmp, == and eq already, but it's not clear to me when each of them is called.
My concrete use case is that I'm writing a Ruby extension using the C API which exposes functions taking (and returning) values of a custom string type (QString, to be precise) which my extension also registers. However, I'd like to make those custom strings behave as intuitive as possible. Unfortunately I can't just return Ruby strings from my C code since it should be possible to call Qt methods on the strings.
There are at least three approaches:
class MyString < String; ...; end
Define #to_s
Define #to_str
Doing both #2 and #3 will make the object act very much like a real String even if it isn't a subclass.
#to_s is an explicit converter, meaning it must appear in Ruby code to work.
#to_str is an implicit converter, meaning the Ruby interpreter will attempt to call it when it wants a String but is given something else.
Update:
Here is an example of some fun you can have with to_str:
begin
open 1, 'r'
rescue TypeError => e
p e
end
class Fixnum
def to_str; to_s; end
end
open 1, 'r'
When run, the first open fails with TypeError but the second proceeds to looking for 1.
#<TypeError: can't convert Fixnum into String>
fun.rb:9:in `initialize': No such file or directory - 1 (Errno::ENOENT)
from fun.rb:9:in `open'
Although it's tempting to sub-class String to give it a new initialize method that will import these QString-type strings, you may just want to tack on an extension to String that helps with the conversion so you don't have to re-implement a version of String itself.
For instance, with two methods you could pretty much have this done:
class String
def self.from_qstring(qstring)
new(...)
end
def to_qstring
# ...
end
end
Having multiple storage types for String is not going to be a problem until you start comparing them, but given that Ruby's String is quite robust, writing a work-alike is difficult.
It's not generally a good idea to subclass classes that were built by someone else in Ruby, because too many things can go wrong. (You might, for example, override an internal method without knowing it.)
1) define Mystring.to_s to get automatic conversion from a Mystring to a String.
2) Not sure what you mean by this. If you want a String method that returns a Mystring, you will have to monkey-patch String:
Class String
def to_mystring
return Mystring.new(self)
end
end
3) to get t == s (assuming s is an instance of String and t an instance of Mystring) define <=>. To get s == t you will have to monkey patch String again, though.
Since I was looking for something similar, but none of the other answers worked for me, I'll post what did work for me.
Found in this blog post which discourage the use of inheriting String and instead use simple delegator.
Inheriting from SimpleDelegator create an object which delegate everything to a string of your choice but on which you add behavior as you see fit.
class ChunkyBacon < SimpleDelegator
def initialize(content)
#content = content
super #content
end
def chunky_bacon?
#content == 'chunky_bacon'
end
end
test = ChunkyBacon.new('choco pizza') # => 'choco pizza'
test.chunky_bacon? # => false

Resources