Ruby method argument default value, implicit vs literal nil, library's responsibility or callers?

时间:2016-02-12 21:30:13

标签: ruby

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.

{album-id}?fields=name,id,privacy,photos.fields(id,privacy)

{
  "name": "Profile Pictures",
  "id": ...,
  "privacy": "everyone",
  "photos": {
    "data": [
      {
        "id": ...
      },
      {
        "id": ...
      }
    ],
  }
}

If you call def tester(input={}) input end you get tester. However if you call {} you get tester(nil).

Honestly that surprised me, I figured you'd get nil if you passed {} to the method, because I always imagined the statement nil in the method definition to be akin to input={}, but clearly it's more like 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:

defined?(input) ? input : {}

So you can call def initialize(input={}) or MyClass.new, but you can't call MyClass.new foo:1

That seems obvious except when using variables to init, eg. MyClass.new nil where opts could be a hash or nil.

I could change the way this works by changing the implementation like so:

MyClass.new(opts)

But, I'm left wondering, is this the right design for a Ruby interface?

Should I instead expect the caller to use def initialize(input=nil) input ||= {} ... end ?

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 MyClass.new(opts || {}) 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?

1 个答案:

答案 0 :(得分:2)

The defaults only apply to unspecified arguments, not those that are supplied but given_number. In Ruby that's a value, an object even, it's not like other languages with "undefined" or "null".

Your observation to use nil is how a lot of libraries handle this sort of thing. I usually design code to be resilient in the face of passing ||= { } 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 nil 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:

= [ ]

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 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 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.