ruby-core@ruby-lang.org archive (unofficial mirror)
 help / color / mirror / Atom feed
From: Ruby-Lang@JoergWMittag.De
To: ruby-core@ruby-lang.org
Subject: [ruby-core:73939] [Ruby trunk Feature#11262] Make more objects behave like "Functions"
Date: Tue, 23 Feb 2016 00:55:34 +0000	[thread overview]
Message-ID: <redmine.journal-57089.20160223005534.67e2be1a540f315e@ruby-lang.org> (raw)
In-Reply-To: redmine.issue-11262.20150615115504@ruby-lang.org

Issue #11262 has been updated by Jörg W Mittag.


Victor Shepelev wrote:
> For me, this thing looks like some kind of over-simplification (leading to ambiguity).
> Both cases are handled with sligtly longer statements with much more clear intent:
> 
> ~~~ruby
> %i[ruby c cplusplus scala java perl].select(&allowed_languages.method(:include?))
> %w[matz ko1 charlie].map(&Person.method(:new))
> ~~~

I don't want to focus too much on the second example, because like I said: the "Class as Factory Function" is *not* actually part of my proposal, I mention it only as food for thought. However, if you have ever seen or implemented a Factory Pattern in Ruby, if you have ever seen or used JavaScript, if you have ever seen or used Dart, if you have ever seen or used one of the many object systems in Scheme or Clojure, if you are familiar with some of the theoretic formalisms for OO, if you have ever read some of [William R. Cook](http://WCook.BlogSpot.Com/)'s [writings on OO](http://CS.UTexas.Edu/~wcook/Drafts/2009/essay.pdf), then the idea that a class is a function that creates objects will look completely natural to you.

What does a class do in Ruby? It holds methods, but that is actually just inherited by classes being special-cases of modules. What distinguishes classes from modules in Ruby is above all [`Class#allocate`](http://ruby-doc.org/core/Class.html#method-i-allocate) and [`Class#new`](http://ruby-doc.org/core/Class.html#method-i-new), i.e. the possibility to create values. And isn't "creating values" basically exactly what a function does?

In other languages, classes are often described as "templates for objects". But that isn't actually true in Ruby. Classes don't describe the shape of objects. Instance variables get added to objects over time, as methods are being called on them, thus objects constantly change their shape over time, the shape is not determined by the class. In fact, all objects start out with the exact same shape, only by calling methods (`initialize` being one such method) on them *after* they have already been constructed by the class do they change their shape.

> This code is DRY, clear and readable even for novice (though can cause some kind of surpise "wow, I could do this?!"). 
> The only "too long" thing here is entire word "method" (so, `map{|s| Person.new(s)}` is a bit shorter, while not being that DRY).

Well, it's not really about saving a few keystrokes. It's basically about semantics: Ruby has a very clear idea about what constitutes a "Function", namely, a "Function" is any object that responds to `call` and `to_proc`. And it's also very clear (at least to me, and I am writing this proposal partly to figure out whether this is true more broadly as well) that an array is a function from integers to elements, a hash is a function from keys to values, and a set is a function from elements to booleans (in fact, as [William R. Cook](http://WCook.BlogSpot.Com/) showed in [*On Understanding Data Abstraction, Revisited*](http://CS.UTexas.Edu/~wcook/Drafts/2009/essay.pdf), identifying sets with their characteristic functions is *the* OO way to implement sets). In other languages, those data structures *literally* inherit from a `Function` class (e.g. in Scala) or implement a `Function` interface (e.g. in Clojure).

----------------------------------------
Feature #11262: Make more objects behave like "Functions"
https://bugs.ruby-lang.org/issues/11262#change-57089

* Author: Jörg W Mittag
* Status: Open
* Priority: Normal
* Assignee: 
----------------------------------------
# What is a Function?

In Ruby, we have the [`Proc`](http://ruby-doc.org/core/Proc.html) 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). 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) 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`](http://ruby-doc.org/core/Array.html) 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) 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
~~~

# `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). 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) 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 @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 `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 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.]



-- 
https://bugs.ruby-lang.org/

  parent reply	other threads:[~2016-02-23  0:20 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
     [not found] <redmine.issue-11262.20150615115504@ruby-lang.org>
2015-06-15 11:55 ` [ruby-core:69590] [CommonRuby - Feature #11262] [Open] Make more objects behave like "Functions" JoergWMittag+Ruby-Lang
2015-07-09  5:10 ` [ruby-core:69906] [CommonRuby - Feature #11262] " 2851820660
2016-02-22 15:27 ` [ruby-core:73919] [Ruby trunk Feature#11262] " Ruby-Lang
2016-02-22 16:50   ` [ruby-core:73927] " Hanlyu Sarang
2016-02-22 16:53     ` [ruby-core:73928] " Recursive Madman
2016-02-22 15:46 ` [ruby-core:73922] " zverok.offline
2016-02-23  0:55 ` Ruby-Lang [this message]
2016-02-23  0:58 ` [ruby-core:73940] " Ruby-Lang
2016-06-24  8:40 ` [ruby-core:76136] " Ruby-Lang
2016-06-24  8:43 ` [ruby-core:76137] " Ruby-Lang

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-list from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.ruby-lang.org/en/community/mailing-lists/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=redmine.journal-57089.20160223005534.67e2be1a540f315e@ruby-lang.org \
    --to=ruby-core@ruby-lang.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).