Feature #18644
openCoerce anything callable to a Proc
Description
Functional objects are increasingly popular in Ruby. Having objects that respond to #call
makes them interchangeable with a Proc
.
However, when you need to perform some Proc-specific operation, like currying, you have to break the abstraction and ask for the type of object. Example:
(callable.is_a?(Proc) ? callable : callable.method(:call)).curry[value]
Because of https://bugs.ruby-lang.org/issues/18620, it's not possible to make them polymorphic by taking the :call
method:
callable.method(:call).curry[value] # won't work!
Consequently, I propose adding a built-in Ruby way to coerce anything callable to a proc (examples in Ruby):
Option 1: Object#to_proc
class Object
def to_proc
return method(:call).to_proc if respond_to?(:call)
raise "Needs to respond to :call"
end
end
class Proc
def to_proc
self
end
end
callable.to_proc.curry[value]
Option 2. Kernel#Proc
class Kernel
def Proc(value)
if value.is_a?(::Proc)
value
elsif value.respond_to?(:call)
value.method(:call).to_proc
else
raise "Needs to implement :call"
end
end
end
Proc(callable).curry[value]
Updated by waiting_for_dev (Marc Busqué) over 2 years ago
- Description updated (diff)
Updated by waiting_for_dev (Marc Busqué) over 2 years ago
- Description updated (diff)
Updated by Eregon (Benoit Daloze) over 2 years ago
As background, to_proc
already exists as a coercion protocol, it's what is used for call(&callable)
, and there is already Proc#to_proc
.
I think Option 1 makes sense and would be good to add, since indeed we can easily produce a Proc from a call-able object via method(:call).to_proc
as shown (there is also -> (*a, **kw) { call(*a, **kw) }
, but that loses arity & parameters information).
Option 2 is IMHO less good, because it wouldn't help for call(&callable)
and doesn't simply use the existing protocol.
Updated by joel@drapper.me (Joel Drapper) over 2 years ago
I really like the first option but unfortunately it would make every object respond to to_proc
even when they don't respond to call
. Perhaps a third option would be for the &
prefix operator to try to coerce using to_proc
and then failing that, try method(:call).to_proc
if the object responds to call
.
Alternatively, there might be a way to provide a default definition for to_proc
on only objects that respond to call
. For example, here's a hacky way to do that in Ruby.
class Object
def method_missing(name, ...)
if name == :to_proc && respond_to?(:call)
method(:call).to_proc
else
super
end
end
def respond_to_missing?(name, ...)
name == :to_proc && respond_to?(:call, ...) || super
end
end