Reputation: 43153
I'm hoping you guys can help me decide which behavior would be more "standard" or "expected."
Background:
In Ruby you can provide a default argument to a method, eg.
def tester(input={})
input
end
If you call tester
you get {}
. However if you call tester(nil)
you get nil
.
Honestly that surprised me, I figured you'd get {}
if you passed nil
to the method, because I always imagined the statement input={}
in the method definition to be akin to input ||= {}
, but clearly it's more like defined?(input) ? input : {}
.
Question:
In a library I maintain I've got a class that takes an optional hash on initialization. The initializer looks just like tester above:
def initialize(input={})
So you can call MyClass.new
or MyClass.new foo:1
, but you can't call MyClass.new nil
That seems obvious except when using variables to init, eg.
MyClass.new(opts)
where opts could be a hash or nil.
I could change the way this works by changing the implementation like so:
def initialize(input=nil)
input ||= {}
...
end
But, I'm left wondering, is this the right design for a Ruby interface?
Should I instead expect the caller to use MyClass.new(opts || {})
?
If you're using a library, and there's a class in that lib that takes an optional hash, would you expect it to be safe to pass nil
to the initializer and for that to be treated as equivalent to passing no argument? Or would you expect that to break because, in Ruby, passing a literal nil is not the same as passing no argument?
Which design feels more "correct" to you?
Upvotes: 1
Views: 2335
Reputation: 211740
The defaults only apply to unspecified arguments, not those that are supplied but nil
. In Ruby that's a value, an object even, it's not like other languages with "undefined" or "null".
Your observation to use ||= { }
is how a lot of libraries handle this sort of thing. I usually design code to be resilient in the face of passing nil
in by accident as this can happen by mistake and seems like a reasonable interpretation of how to do what I mean without being overly paranoid and defensive.
Normally this is only an issue with = { }
or = [ ]
type situations. In a sense this is the Ruby principle of passing in something that's close enough to work, or at least not incorrect. Think of how the method might be used and accommodate all reasonable situations.
So the signatures you tend to see look like this:
def with_options(args = nil)
# Default arguments to an empty Hash
args ||= { }
# ...
end
def trim(string, length = 1)
string = string.to_s # Coerce to string
length = length.to_i # Coerce to integer
# ...
end
For code that's used internally it's generally best to be more strict and throw errors if the methods are used incorrectly, that is be less defensive, but for methods that are to be used by a variety of parties that may not get everything perfectly correct, a degree of latitude on what comes in is always appreciated.
Passing in "1"
instead of 1
for a numerical argument is all too easy in an environment like Rails, and punishing someone for not casting properly is perhaps too cruel. At least try and convert and deal with the result.
Upvotes: 4