开发者

Ruby case statement with multiple variables using an Array

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:

开发者_Python百科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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜