Project

General

Profile

Actions

Feature #18644

open

Coerce anything callable to a Proc

Added by waiting_for_dev (Marc Busqué) 3 months ago. Updated 9 days ago.

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

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]
Actions #1

Updated by waiting_for_dev (Marc Busqué) 3 months ago

  • Description updated (diff)
Actions #2

Updated by waiting_for_dev (Marc Busqué) 3 months ago

  • Description updated (diff)

Updated by Eregon (Benoit Daloze) 3 months 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) 9 days 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
Actions

Also available in: Atom PDF