Ruby case statement with multiple variables using an Array - ruby

I'd like to compare multiple variables for a case statement, and am currently thinking overriding the case equals operator (===) for Array is the best way to do it. Is this the best way?
Here is an example use case:
def deposit_apr deposit,apr
# deposit: can be nil or 2 length Array of [nil or Float, String]
# apr: can be nil or Float
case [deposit,apr]
when [[Float,String],Float]
puts "#{deposit[0]} #{deposit[1]}, #{apr*100.0}% APR"
when [[nil,String],Float]
puts "#{apr*100.0}% APR on deposits greater than 100 #{deposit[1]}"
when [[Float,String],nil]
puts "#{deposit[0]} #{deposit[1]}"
else
puts 'N/A'
end
end
The only problem is the Array case equals operator doesn't apply the case equal to the elements of the Array.
ruby-1.9.2-p0 > deposit_apr([656.00,'rupees'],0.065)
N/A
It will if I override, but am not sure what I'd be breaking if I did:
class Array
def ===(other)
result = true
self.zip(other) {|bp,ap| result &&= bp === ap}
result
end
end
Now, it all works:
ruby-1.9.2-p0 > deposit_apr([656.00,'rupees'],0.065)
656.0 rupees, 6.5% APR
Am I missing something?

I found this question because I was looking to run a case statement on multiple variables, but, going through the following, came to the conclusion that needing to compare multiple variables might suggest that a different approach is needed. (I went back to my own code with this conclusion, and found that even a Hash is helping me write code that is easier to understand.)
Gems today use "no monkey patching" as a selling point. Overriding an operator is probably not the right approach. Monkey patching is great for experimentation, but it's too easy for things to go awry.
Also, there's a lot of type-checking. In a language that is designed for Duck Typing, this clearly indicates the need for a different approach. For example, what happens if I pass in integer values instead of floats? We'd get an 'N/A', even though that's not likely what we're looking for.
You'll notice that the example given in the question is difficult to read. We should be able to find a way to represent this logic more clearly to the reader (and to the writer, when they revisit the code again in a few months and have to puzzle out what's going on).
And finally, since there are multiple numbers with associated logic, it seems like there's at least one value object-type class (Deposit) that wants to be written.
For cleanliness, I'm going to assume that a nil APR can be considered a 0.0% APR.
class Deposit
def initialize(amount, unit='USD', options={})
#amount = amount.to_f # `nil` => 0.0
#unit = unit.to_s # Example assumes unit is always present
#apr = options.fetch(:apr, 0.0).to_f # `apr: nil` => 0.0
end
end
Once we have our Deposit object, we can implement the print logic without needing case statements at all.
class Deposit
# ... lines omitted
def to_s
string = "#{#amount} #{#unit}"
string << ", #{#apr * 100.0}% APR" if #apr > 0.0
string
end
end
d = Deposit.new(656.00, 'rupees', apr: 0.065)
d.to_s
# => "656.0 rupees, 6.5% APR"
e = Deposit.new(100, 'USD', apr: nil)
e.to_s
# => "100.0 USD"
f = Deposit.new(100, 'USD')
f.to_s
# => "100.0 USD"
Conclusion: If you're comparing multiple variables in a case statement, use that as a smell to suggest a deeper design issue. Multiple-variable cases might indicate that there's an object that wants to be created.

If you are worried about breaking something by changing Array behavior, and certainly that's a reasonable worry, then just put your revised operator in a subclass of Array.

it's definitely not the best way. even more - you should not redefine methods of standart classes as core functionality may depend on it - have fun debugging then.
defensive style is nice(with lot of type checks and whatnot) but it usually hurts performance and readability.
if you know that you will not pass anything else than bunch of floats and strings to that method - why do you need all those checks for?
IMO use exception catching and fix the source of problem, don't try to fix the problem somewhere in the middle

Related

Detecting a missing sub!, sort!, map!, etc

After returning to Ruby from a long stint coding in another language, I regularly assume that foo.sort, foo.map {...}, foo.sub /bar/, 'zip' will change foo. Of course I meant foo.sort!, etc. But that usually takes 3 or 4 debugging potshots before I notice. Meanwhile, the sort is calculated, but then isn't assigned to anything. Can I make ruby warn about that missing lvalue, like a C compiler warns of a function's ignored return value?
You mean like Perl's somewhat infamous "using map in void context"? I don't know of Ruby having such a thing. Sounds like you need more unit testing to catch mistakes like this before they can worm into your code deeply enough to be considered bugs.
Keep in mind Ruby's a lot more flexible than languages like Perl. For example, the following code might be useful:
def rewrite(list)
list.map do |row|
row += '!'
end
end
Now technically that's a map in a void context, but because it's used as a return value it might be captured elsewhere. It's the responsibility of the caller to make use of it. Flagging the method itself for some sort of warning is a level removed from what most linting type tools can do.
Here's a very basic parser :
#forgetful_methods = %w(sort map sub)
Dir['*.rb'].each do |script|
File.readlines(script).each.with_index(1) do |line, i|
#forgetful_methods.each do |method|
if line =~ /\.#{method}(?!!)/ && $` !~ /(=|\b(puts|print|return)\b|^#)/
puts format('%-25s (%3d) : %s', script, i, line.strip)
end
end
end
end
# =>
# brace_globbing.rb ( 13) : subpatterns.map{|subpattern| explode_extglob(match.pre_match+subpattern+match.post_match)}.flatten
# delegate.rb ( 11) : #targets.map { |t| t.send(m, *args) }
It checks every ruby script in the current directory for sort, map or sub without ! that aren't preceded by =, puts, print or return.
It's just a start, but maybe it could help you find some of the low hanging fruits.
There are many false positives, though.
A more complex version could use abstract syntax trees, for example with Ripper.

What is the purpose of `Array#include?` as compared to `Array#index`?

Array#include? provides only a weaker information than what Array#index provides, i.e., when Array#index returns nil, the corresponding method call with Array#include? will return false, and when Array#index returns an integer, Array#include? returns true. Furthermore, comparing the two indicates that there is no significant difference in speed; rather Array#index often shows a better result than Array#include?:
a = %w[boo zoo foo bar]
t = Time.now
10000.times do
a.include?("foo")
end
puts Time.now - t # => 0.005626235
t = Time.now
10000.times do
a.index("foo")
end
puts Time.now - t # => 0.003683945
Then, what is the purpose of Array#include?? Can't all code using it be rewritten using Array#index?
I know this isn't an official reason, but I can think of a few things:
Clarity: as a name, include? makes more sense at first sight, and also allows easy visual confirmation of code correctness by identifying itself as a boolean predicate. This follows the concept of making wrong code look wrong (see http://www.joelonsoftware.com/articles/Wrong.html)
Good typing: If all you want is a boolean value for a boolean check, making that a number could lead to bugs
Cleanliness: Isn't it nicer to see a printed output of "true" rather than going back to C and having no boolean to speak of?

Why ruby constants are changable? What is the difference between variable?

I was learning ruby, and i learnt that ruby constants must start with a Upper case letter (e.g. Myconstant). This will make it a constant, but its value is changeable!
If a constant's value is changeable then why do we need constant, what is the difference between variable then?
Constants have lexical scoping, whereas methods have dynamic scoping:
class Super
Constant = "Super::Constant"
def method
'Super#method'
end
def get_constant
Constant
end
def get_method
method
end
end
class Sub < Super
Constant = 'Sub::Constant'
def method
'Sub#method'
end
end
Super.new.get_constant # => "Super::Constant"
Sub.new.get_constant # => "Super::Constant"
Super.new.get_method # => "Super#method"
Sub.new.get_method # => "Sub#method"
And as far as variables, they are inaccessible from the outside. How would you intend to access these?
class Object
Constant = 'constant'
local_var = 'local var'
#instance_var = 'instance var'
##class_var = 'class var' # btw, never use these
end
Also, there's a lot of things you can do in Ruby, but for your own sanity, be wary. I'd recommend against changing constants around, it will likely frustrate your team.
Ruby lets you shoot yourself in the foot (if you really want to). But, at least in this case, it warns you about it.
ONE = 'one'
ONE = 'two' # !> already initialized constant ONE
Some reasons:
1) Convention. It's easy to see just from the name of an identifier that it's not supposed to change.
2) Technical. It (probably; someone more knowledgeable than I will probably answer) makes the interpreter simpler.
3) Dynamism is sometimes helpful; in testing, for example, it's possible to redefine things for testing purposes rather than having to stub/proxy everything…
I use this feature sometimes to test out code without otherwise necessary parameters, eg when i run the script from my editor where it is difficult to provide a parameter.
#ARGV[0] = "c:/test.txt" #in case of testing i remove the first remark sign

How to create an operator for deep copy/cloning of objects in Ruby?

I would like to achieve the following by introducing a new operator (e.g. :=)
a := b = {}
b[1] = 2
p a # => {}
p b # => {1=>2}
As far as I understand, I need to modify the Object class, but I don't know what to do in order to get what I want.
require 'superators'
class Object
superator ":=" operand # update, must be: superator ":=" do |operand|
# self = Marshal.load(Marshal.dump(operand)) # ???
end
end
Could you help me with this?
Update
Ok, superators will probably not help me here, but I still want such operator. How can I (or you) create an extension for Ruby, which I could load as a module?
require 'deep_copy_operator'
a !?= b = {} # I would prefer ":=" but don't really care how it is spelled
b[1] = 2
p a # => {}
p b # => {1=>2}
wow, superators look neat! But unfortunately, this won't work for you, for two reasons. First, your operator does not match the regex (you cannot use a colon). Easy enough, find a new operator. But the second one I don't think can be overcome, the superator is basically a method name defined on the object to the left. So you can't use it for assignment statements. If your variable is not defined, then you cannot use it, that would raise an error. And if it is defined, then you can't change its type in any way that is obvious to me (maybe with some level of reflection and metaprogramming that is way beyond anything I know, but it honestly seems unlikely... of course, I would have never expected it to be possible to create superators, so who knows).
So I think you're back to hacking parse.y and rebuilding your Ruby.
First of all, the syntax for superators is
superator ":=" do |operand|
#code
end
It's a block, because superator is a metaprogramming macro.
Secondly, you have something going their with Marshal...but it's a bit of magic-ish. Feel free to use it as long as you understand exactly what it is you're doing.
Thirdly, what you are doing isn't quite doable with a superator (I believe), because self cannot be modified during a function. (if someone knows otherwise, please let me know)
Also, in your example, a must first exist and be defined before being able to call the method := in it.
Your best bet is probably:
class Object
def deep_clone
Marshal::load(Marshal.dump(self))
end
end
to generate a deep clone of an object.
a = (b = {}).deep_clone
b[1] = 2
p a # => {}
p b # => {1=>2}

Type Conversions in Ruby: The "Right" Way?

I am trying to decide if a string is a number in Ruby. This is my code
whatAmI = "32.3a22"
puts "This is always false " + String(whatAmI.is_a?(Fixnum));
isNum = false;
begin
Float(whatAmI)
isNum = true;
rescue Exception => e
puts "What does Ruby say? " + e
isNum = false;
end
puts isNum
I realize that I can do it with a RegEx, but is there any standard way to do it that I'm missing? I've seen a can_convert? method, but I don't seem to have it.
Is there a way to add a can_convert? method to all Strings? I understand that it's possible in Ruby. I also understand that this may be totally unecessary...
Edit The to_f methods do not work, as they never throw an Exception but rather just return 0 if it doesn't work out.
You've got the right idea. It can be done a little more compactly, though:
isNum = Float(whatAmI) rescue nil
Inlining "rescue" is great fun. Parenthesize the part you're rescuing if you put it in the middle of more stuff, like:
if (isNum = Float(whatAmI) rescue nil) && isNum > 20
...
This might sound overly critical, but based on your description, you're almost certainly doing something "wrong"... Why would you need to check whether a String is a valid representation of a Float but not actually convert the String?
This is just a hunch, but it may be useful to describe what you're trying to accomplish. There's probably a "better" way.
For example, you might want to insert the String into a database and put together an sql statement using string concatenation. It would be preferable to convert to a Float and use prepared statements.
If you just need to inspect the "shape" of the string, it would be preferable to use a regular expression, because, as dylan pointed out, a Float can be represented in any number of ways that may or may not be appropriate.
You may also have to deal with international input, in Europe, a comma is used instead of a period as decimal seperator, yet Ruby Floats have to use the (.).
I don't know about the can_convert method, but generally a class will define methods like to_s, to_i, to_hash and so on. These methods return a different representation of the object, for example, a to_s method will return a string representation of an object.
As for the actual converting, I'm sure someone else can answer that better than me.
On another note, if you're wondering if you can add a method to all strings, you can. Just open up the String class and add it!
class String
def can_convert?
...
end
end
Edit: Strings containing exponential notation are supported by to_f.
>> "6.02e23".to_f
=> 6.02e+23

Resources