ruby picture

RCR 298: Blocks as first class values

Submitted by marian (Sun Apr 03 21:01:06 UTC 2005)

Abstract

Ruby claims to be a pure object oriented language. However, its closures management is quite primitive. Closures are not considered objects, that is why operations for conversion and special sintax are required. I propose an implementation of closures as first class values in the language.

Problem

Ruby claims to be a pure object oriented language. However, its closures management is quite primitive. Closures are not considered objects, that is why operations for conversion and special syntax are required. This breaks encapsulation and limits their use. In particular, Ruby provides a class Proc to support objectified closures. The problem is that the creation of a Proc object requires the primitive block of code as a parameter:

id = Proc.new{ |x| x } <-- a primitive object is being sent

This kind of conversion has to be done each time we want to handle closures as objects, i.e. to apply a same anonymous piece of code to different objects.

What's more, special syntax is required to indicate a block parameter converted to a Proc (&block). Besides, the operation yield is used in case the method call is associated with a block when it is called, although the same effect could be achieved passing the Proc object explicitly and then call the method call on it (as in any functional language: the function is passed explicitly and then applied conveniently).

Proposal

Remove primitive blocks and provide sintax sugar to construct Proc objects. Example:

id = {|x| x} <-- the Proc object

In this case the braces are the syntax sugar provided for the creation of Proc objects (the sintax may have to change as there may be ambiguity with hash tables creation).

Now when you want to pass the block in a method call:

def f(block) <-- the & is no more required

    block.call 2          <-- yield is no more required
end

f id <-- the call f {|x| x} <-- other call

Analysis

Blocks are not primitive anymore. The only way to create closures is creating a Proc object and syntax sugar is provided for the instantiation. I think this resembles the way Smalltalk and any functional language successfully treat closures.

PD: if you want to keep the yield operation (I don't see why really) you can, but now yield operates on an implicit Proc object.

def f

    yield 2
end

f {|x| x} <-- a Proc object is being sent

Implementation

The parser and evaluation rutines should change.
ruby picture
Comments Current voting
Sorry, but this is stupid. Yield is a nice way of dealing with the 95% case -- when only one block is required. If you want to pass a block, you don't need the &block format -- it can be provided just as a variable, e.g.:

  def foo(myproc)
    proc.call(5)
  end
  foo(lambda { |x| puts x })

The &block form is only used to convert the 95% case when you need to propagate certain information.

Additionally, there's an ongoing discussion about what might be done to simplify and further embed block definition on ruby-talk. Ruby 1.9 has some of what you're talking about.

Not only that, much of your RCR isn't in the form that Matz has requested. Other than this vague concept that blocks should be "first class objects" -- which IMO is debatable -- there's no justification given. Tell us why this matters. Don't just give me theoretics; give me specific places that this really makes a difference. --Austin Ziegler


Sometimes It's not all about being pragmatic. I still think that blocks support is not well designed. In Ruby, numbers are objects, arrays are objects, blocks are .... argh!!. The same effects may be achieved using primitive blocks, but that doesn't mean that's the best way to implement them. What's more, you have two entities that represent the same thing: the primitive block and the Proc class. I think that is not good.


Blocks *are* objects in Ruby. Just because the common case makes them seem non-object doesn't change that they are able to be treated as objects. If you're going to use an implicit feature, don't be surprised when you have to do something to make it explicit. There are definite improvements to block and proc handling to be made -- some of those experiments are visible in the 1.9 tree. But getting rid of "yield" and forcing the declaration of block variables on everything is bad. --AZ


If they are objects, why is block to Proc conversion required? What messages does an object understand? Please don't get angry, but I don't see it (maybe because I'm new to Ruby). I'm not sure about getting rid of "yield" (you may be right on that), but I don't see blocks as objects in the language.--marian


I'm sorry. I had been using Ruby version 1.8. I've just tried version 1.9, and blocks managment feels much, much better.--marian


Blocks are objects; if you want to use them explicitly, you need to have variables to deal with them, e.g., the form:

  def foo(&block)
  end

A common idiom -- particularly in my code -- is the following:

  def initialize
    # ... object initialization
    yield self if block_given?
  end

To convert it to the form required by getting rid of yield would mean:

  def initialize(&block)
    # ... object initialization
    block.call(self) if block
  end

In the former, I am yielding execution to the block. In the latter, I am calling the block. In actual code executed, there may be no difference, but in terms of feel, there's significant difference.

I haven't played with the block management in 1.9, but I have not liked what I have seen shown, as it makes the empty parameter (e.g., ignored parameters) case harder, e.g.:

  lambda { x } # Standard case
  { x }        # 1.9 -- might be hash or block; default to hash
  {|| x }      # 1.9 -- not the same as "standard case"
  {|*y| x }    # 1.9 -- sort of the same as "standard case"

I don't see any upside to the changed block semantics unless hash literal creation is also changed, but that will break literally thousands of programs that have come to use the Ruby 1.x hash literal creation idiom. This will also (potentially?) break programs that use parameter-less procs.

Again, consider the 95% case: 95% of the time, if you are implementing an #each, you won't need to access the provided block as an object -- you won't need to #call it. In the 5% case where you do, then you can force a conversion with &name in the parameter.

I strongly recommend that in the future, you become more familiar with the language and its idioms before recommending a change to the core language. We all do it at some point or another (I did it about 30 days in with an idea regarding boolean RHS results allowing the chaining of otherwise boolean operators, e.g., 1 < x < 5).

-austin


If you're new to Ruby and not clear about something, post the RCR to ruby-talk first before bringing it up here. RCR's are supposed to be discussed by the community before being submitted here. A post to ruby-talk would surely have shown you that procs _are_ first-class objects in Ruby.


I am strongly in favour of first-order functions/blocks/methods, but this is not the right way to do it. There is no reason to abandon #yield and implicit blocks.


It looks like this RCR is asking to remove the current block syntax - which is clearly the wrong thing to ask - it would break almost everything. But, it is also asking for being able to remove the "proc", "lambda", or "Proc.new". I think this is a fine thing and has indeed been done in v1.9. I won't vote because removing existing syntax is bad, but the new syntax is good (and is being implemented).

Eric


Strongly opposed 6
Opposed 6
Neutral 1
In favor 0
Strongly advocate 1
ruby picture
If you have registered at RCRchive, you may now sign in below. If you have not registered, you may sign up for a username and password. Registering enables you to submit new RCRs, and vote and leave comments on existing RCRs.
Your username:
Your password:

ruby picture

Powered by .