Project

General

Profile

Actions

Feature #11262

open

Make more objects behave like "Functions"

Added by jwmittag (Jörg W Mittag) over 7 years ago. Updated almost 3 years ago.

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

Description

What is a Function?

In Ruby, we have the Proc class to represent objects which are "function-like". But, in true object-oriented / duck-typing fashion, an object doesn't actually have to be an instance of Proc in order to be treated as a function, it only needs to respond to call. For cases, where a Proc instance is absolutely required (mostly, the & unary prefix ampersand "make-me-a-block" operator), there is the to_proc conversion.

So, in short: if an object wants to be a function, it MUST respond to call, and SHOULD also respond to to_proc.

There are some objects in Ruby that could be seen as functions, but currently don't respond to call or to_proc:

Array as mapping

An array is a mapping from indices to elements. "Mapping" is just a different word for (partial) function, though! I propose, that Array should implement call and to_proc in the following manner:

class Array
  alias_method :call, :[]

  def to_proc
    method(:call).to_proc
  end
end

Hash as mapping

A hash is a mapping from keys to values. I propose, that Hash should implement call and to_proc in the following manner:

class Hash
  alias_method :call, :[]

  def to_proc
    method(:call).to_proc
  end
end

[Note: #11653 implements the to_proc part of this proposal.]

Set as predicate

A set is a mapping from values to booleans, i.e. a set is the same as its include? predicate. This would mean, for example, that I can pass a Set as a predicate to methods like Enumerable#select. I propose, that Set should implement call and to_proc in the following manner:

require 'set'

class Set
  alias_method :call, :include?

  def to_proc
    method(:call).to_proc
  end
end

I believe that these three additions are worthwhile and fairly uncontroversial. They match with the way arrays, maps and especially sets are treated in mathematics and in other programming languages. E.g. in both Clojure and Scala, arrays, sets and maps are functions and use function application syntax for accessing values. Scala doesn't even have indexing syntax.

Here are some potential use cases:

numbers_to_words = %w[zero one two three four five six seven eight nine ten eleven twelve]

[4, 7, 1, 0, 8].map(&numbers_to_words)
# => ['four', 'seven', 'one', 'zero', 'eight']


allowed_languages = Set[:ruby, :python, :scala, :scheme]

%i[ruby c cplusplus scala java perl].select(&allowed_languages)
# => [:ruby, :scala]

Here is a more "wild" proposal that is much more controversial. I don't actually propose adding this to Ruby, but I will mention it here as food for thought:

Class as factory

If you squint your eyes, tilt your head sideways and look at it juuuuuuust right, a class is a factory for objects. In other words, it is a function from constructor arguments to instances:

class Class
  alias_method :call, :new

  def to_proc
    method(:call).to_proc
  end
end

Example:

class Person
  def initialize(name)
    @name = name
  end
end

%w[matz ko1 charlie].map(&Person)
# => [#<Person:0xdeadbeef481523 @name="matz">, #<Person:0xdeadbeef815234 @name="ko1">, #<Person:0xdeadbeef152342 @name="charlie">]

Incompatibilities

This proposal conflicts with #10829, which proposes to use Array#to_proc for a completely different purpose.

I believe that having Arrays behave as functions from indices to elements is natural, unsurprising, and well in line with both mathematics and other languages.


Related

  1. #11653 implements a small subset of my proposal.

  2. The code duplication encountered here suggests refactoring to extract two new mixins in the Ruby core library:

    module Callable
      def to_proc
        method(:call).to_proc
      end
    end
    
    module Indexable
      alias_method :call, :[]
    end
    

    However, this is out of scope of this discussion and not part of this particular feature proposal.


[NOTE: I originally posted this in project:common-ruby, which according to [[common-ruby:|its wiki]] is "The official place to submit feature proposal for Ruby" but from my observation, almost all Ruby feature requests actually get filed at Ruby master.]

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0