Project

General

Profile

Feature #11262

Updated by jwmittag (Jörg W Mittag) about 4 years ago

# What is a Function? 

 In Ruby, we have the [`Proc`](http://ruby-doc.org/core/Proc.html) `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`](http://ruby-doc.org/core/Proc.html#method-i-call). `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`](http://ruby-doc.org/core/Proc.html#method-i-to_proc) `to_proc` conversion. 

 So, in short: if an object wants to be a function, it **MUST** must respond to `call`, and **SHOULD** 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`](http://ruby-doc.org/core/Array.html) `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: 

 ~~~ruby 
 

     class Array 
   
       alias_method :call, :[] 

   

       def to_proc 
     
         method(:call).to_proc 
   
       end 
 
     end 
 ~~~ 

 # [`Hash`](http://ruby-doc.org/core/Hash.html) `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: 

 ~~~ruby 
 

     class Hash 
   
       alias_method :call, :[] 

   

       def to_proc 
     
         method(:call).to_proc 
   
       end 
 
     end 
 ~~~ 

 Note the duplication here. This suggests maybe refactoring to break out an `Indexable` mixin that is included by both `Array` and `Hash`. However, this is out of scope of this particular 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`](http://ruby-doc.org/core/Enumerable.html#method-i-select). `Enumerable#select`. I propose, that `Set` should implement `call` and `to_proc` in the following manner: 

 ~~~ruby 
 

     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](http://clojure.org/data_structures#Data%20Structures-Maps%20(IPersistentMap)) and [Scala](http://scala-lang.org/api/current/#scala.collection.Seq), 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: 

 ~~~ruby 
 

     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`](http://ruby-doc.org/core/Class.html) `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: 

 ~~~ruby 
 

     class Class 
   
       alias_method :call, :new 

   

       def to_proc 
     
         method(:call).to_proc 
   
       end 
 
     end 
 ~~~ 

 Example: 

 ~~~ruby 
 

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

 

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

 # Incompatibilities 

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

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

 --- 

 # Related 

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

 ~~~ruby 
 

     module Callable 
   
       def to_proc 
     
         method(:call).to_proc 
   
       end 
 
     end 

 

     module Indexable 
   
       alias_method :call, :[] 
 
     end 
 ~~~ 

 However, this is out of scope of for 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 project:ruby-trunk.] request.

Back