Project

General

Profile

Actions

Feature #17844

open

Support list of methods to test with respond_to?

Added by svoop (Sven Schwyn) 5 months ago. Updated 5 months ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:103684]

Description

Not sure whether this is a good idea at all, but I guess it doesn't hurt to put it up for debate.

The preferred way to check e.g. whether an argument is acceptable is by use of respond_to?:

# Don't
def notify(recipient)
  raise ArgumentError unless recipient.instance_of?(User) || recipient.instance_of?(Follower) 
  ...
end

# Do
def notify(recipient)
  raise ArgumentError unless recipient.respond_to? :email
  ...
end

However, sometimes the tested object has to respond to more than one method in order to be acceptable:

def notify(recipient)
  raise ArgumentError unless recipient.respond_to?(:email) && recipient.respond_to?(:name)
  ...
end

The refactored version doesn't look much nicer:

def notify(recipient)
  raise ArgumentError unless %i(email name).reduce(true) do |memo, method| 
    memo &&= recipient.respond_to? method 
  end
  ...

The limiting factor here is respond_to? which only accepts one method as String or Symbol. How about extending it to accept an Array (of String or Symbol) as well?

def notify(recipient)
  raise ArgumentError unless recipient.respond_to? %i(email name)
  ...

Even nicer, but more complicated to implement due to the last and optional argument include_all:

def notify(recipient)
  raise ArgumentError unless recipient.respond_to?(:email, :name)
  ...

What do you think?

Updated by matheusrich (Matheus Richard) 5 months ago

I would be confused whether it should respond to ALL of the params, or just ANY of them. It could be just me, though.

Maybe adding a respond_to_any? would solve that.

Updated by svoop (Sven Schwyn) 5 months ago

Fair point, but I don't think respond_to_any? is a real use case given respond_to? is mostly used to check whether an object implements the necessary interface: The information "it implements method1 OR method2" has little practical value.

Also, the predominant assumption made by other methods accepting one or many args appears to be AND (additive):

  • Array#push
  • Array#union
  • Hash#slice
  • (many more)

TL;DR It should not be a surprise for respond_to?(:email, :name) to returns true if and only if both methods are implemented on the receiver.

Updated by byroot (Jean Boussier) 5 months ago

Currently the method signature is respond_to?(symbol, include_all=false), so changing it to what you suggest might break backward compatibility. e.g.:

>> Object.new.respond_to?(:puts, :bar)
=> true

Of course the above is quite a stretch, but I figured it was worth making it clear.

Updated by svoop (Sven Schwyn) 5 months ago

byroot (Jean Boussier) You're right, breaking backward compatibility of the signature is out of the question. Not sure for C, but in plain Ruby, it's not a problem:

def just_checkin(*methods, include_all: false)
  puts methods.inspect, include_all.inspect
end

just_checkin(:a)
[:a]
false

just_checkin(:a, include_all: true)
[:a]
true

just_checkin(:a, :b)
[:a, :b]
false

just_checkin(:a, :b, include_all: true)
[:a, :b]
true

The alternative (multiple methods must be passed as an Array) for sure don't break anything.

Updated by byroot (Jean Boussier) 5 months ago

svoop (Sven Schwyn), you seem to have missed that include_all is a positional parameter, not a named one.

Updated by Eregon (Benoit Daloze) 5 months ago

I think raise ArgumentError unless recipient.respond_to?(:email) && recipient.respond_to?(:name) is good enough.
It's also likely to be significantly faster than recipient.respond_to_all?(:email, :name) since that has variable arguments, would need more complex caching, etc.

I think that pattern is also not frequent enough to deserve such a ad-hoc shortcut.

Or if there are many symbols, you can use %i[email name].all? { recipient.respond_to?(_1) } which is much shorter and clearer than the reduce variant (don't use reduce for that).
That won't be as fast, but it's concise and clear and needs no new API.

Updated by svoop (Sven Schwyn) 5 months ago

byroot (Jean Boussier) You're right of course, I'm so tuned in to keyword arguments. :-) Can be done in Ruby as def respond_to?(*args), but you'd have to check the last args member for type. It might be more ore less ugly in C, I honestly know zilch about C. At least code like if(flag != Qtrue && flag != Qfalse) is already present in the Ruby source.

Eregon (Benoit Daloze) I like your all? approach a lot, definitely better and more self-explanatory than reduce!

Actions

Also available in: Atom PDF