RCR enumerable extra into core

I’m considering suggesting that the base functionality for the
enumerable-extra gem[1] be submitted into core.

In a nut shell, it’s that you can do

list.map :name

as an alternative to

list.map &:name

which is arguably less readable.
Thoughts?
-r
[1] http://allgems.ruby-forum.com/docs/enumerable-extra/0.1.2/doc/hanna/

list.map :name

Makes sense, given that you can already do

list.reduce(:+)

Tor Erik

Hi –

On Sun, 8 Nov 2009, Roger P. wrote:

which is arguably less readable.
Thoughts?

I’m very much in sympathy with the position that &:name is ugly. It’s
always struck me as uncharacteristically line-noise-ish for Ruby
(though I also understand the motivation for it). I have trouble
talking myself into using it.

At the same time, the merit of &:name over :name is that &:name does
tell you what’s going on, once you know that the & in front of any
object (symbol or otherwise) in last-argument position means that the
object will be converted to a Proc and play the block role.

Having :name magically understood as &:name is, for my taste, too much
invisible ink. It would also mean that map (and maybe other Enumerable
methods) had one argument syntax and all other iterators had another,
since presumably there’s no general way to make method(:sym) know when
it’s supposed to actually mean method(&:sym). I don’t think that’s a
useful special case.

(I know there’s inject(:method), but note that that’s a different
case; inject(:method) is not shorthand for inject(&:method).)

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

On Sun, 8 Nov 2009, David A. Black wrote:

(I know there’s inject(:method), but note that that’s a different
case; inject(:method) is not shorthand for inject(&:method).)

I garbled that bit, due to an irb session gone horribly wrong :slight_smile: So
forget that sentence. I do have a feeling there’s something my brain
is groping toward that differentiates the inject case, but I could be
totally wrong about that.

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

On Nov 8, 11:57 am, “David A. Black” [email protected] wrote:

Having :name magically understood as &:name is, for my taste, too much
invisible ink. […]

I think list.map :name
is clearly to be understood as list.map { |x| x.send(:name) }
not as an abbreviation for list.map &:name
which I agree is extraordinarily ugly.

I think (don’t quote me) allowing #map to take a symbol was debated
and rejected a long time ago. I’d like to see it implemented in core,
as it’s a shortcut for a common idiom, reads nicely and is
unambiguous. I don’t know when list.inject(:+) started working, but
it’s a good thing!

One thing occurred to me, though. Using “send” in the implementation
would allow access to private methods. A check would need to be made
before pulling the trigger.

list.map :name
+1

Hi –

On Sun, 8 Nov 2009, Gavin S. wrote:

object will be converted to a Proc and play the block role.
and rejected a long time ago.
It was: http://oldrcrs.rubypal.com/rejected.html#rcr50. Needless to
say that doesn’t contractually bind Matz to any eternal decision :slight_smile:

There’s no doubt it looks better than &:sym. My problem with it is
that it adds a special case (or a small subclass of special cases). I
don’t think it would be a disaster, but I always dislike things where
it feels like it has to be accounted for sort of inside out (there’s
the general case of &object, the common case of &:sym, and now a kind
of escape from &:sym via :sym).

Mind you, I really don’t think it would be a calamity, just a little
hard to account for in terms of what’s around it.

I’d like to see it implemented in core, as it’s a shortcut for a
common idiom, reads nicely and is unambiguous. I don’t know when
list.inject(:+) started working, but it’s a good thing!

One thing occurred to me, though. Using “send” in the implementation
would allow access to private methods. A check would need to be made
before pulling the trigger.

You could use public_send.

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

Hi –

On Mon, 9 Nov 2009, Rick DeNatale wrote:

I was surprised at how many times an array was used in a context which
expected the pre-1.9 behavior.

When I explained to the client that so many changes were caused
because Ruby 1.9 changed Array#to_s his response was “Why did they do
that?”

Of course the upside of such changes is that they generate billable
hours, but I’d rather generate more value per hour. But c’est la vie!

I’ll take on all comers in the matter of being conservative about
changes to Ruby :slight_smile: Part of the case with the examples you’re
referring to, though, is the fact that 1.9 is more of a major-number
change, in spirit, than it sounds like.

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

which is arguably less readable.
Thoughts?

At the same time, the merit of &:name over :name is that &:name does
tell you what’s going on, once you know that the & in front of any
object (symbol or otherwise) in last-argument position means that the
object will be converted to a Proc and play the block role.

Hmm. I suppose I’m of a slightly different opinion–to me
list.map &:symbol

is less clear than
list.map(:symbol)

If we were required to write
list.map &:symbol.to_proc

then I would be more easily convinced that list.map(:symbol) is too
implicit. So for me it’s implicit already.

I’m not too worried about the run time slowdown…so many methods are
already special cased…it doesn’t seem to be a too large concern to the
core guys…

-r

Roger P. wrote:

which is arguably less readable.
Thoughts?

At the same time, the merit of &:name over :name is that &:name does
tell you what’s going on, once you know that the & in front of any
object (symbol or otherwise) in last-argument position means that the
object will be converted to a Proc and play the block role.

Hmm. I suppose I’m of a slightly different opinion–to me
list.map &:symbol

is less clear than
list.map(:symbol)

If we were required to write
list.map &:symbol.to_proc

But we are. &:symbol is equivalent to :symbol.to_proc. So the & form
is explicit – and thus arguably clearer. I would almost expect
list.map :symbol to return :symbol for each item in list.

then I would be more easily convinced that list.map(:symbol) is too
implicit. So for me it’s implicit already.

Because you’re misunderstanding the syntax.

I’m not too worried about the run time slowdown…so many methods are
already special cased…it doesn’t seem to be a too large concern to the
core guys…

-r

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

list.map(:symbol) is just like object.send(:symbol),except the :symbol
for list.
so list.map(:symbol) is good~

On Sun, Nov 8, 2009 at 9:39 AM, David A. Black [email protected]
wrote:

Mind you, I really don’t think it would be a calamity, just a little
hard to account for in terms of what’s around it.

Not completely on-point, but I tend to be rather conservative when
thinking about such proposed changes.

I’ve recently taken over an existing Rails app, one of the first tasks
was to get it to run with Ruby 1.9.

Most of the changes I needed to make turned out to do with the fact
that 1.9 redefined Array#to_s

Where prior to 1.9 Array#to_s was the same as Array#join so:

[1, nil, “foo”].to_s => “1foo”

in 1.9 it’s been redefined to be the same as Array#inspect so:

[1, nil, “foo”].to_s => “[1, nil, “foo”]”

I was surprised at how many times an array was used in a context which
expected the pre-1.9 behavior.

When I explained to the client that so many changes were caused
because Ruby 1.9 changed Array#to_s his response was “Why did they do
that?”

Of course the upside of such changes is that they generate billable
hours, but I’d rather generate more value per hour. But c’est la vie!

Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

Haoqi H. wrote:

list.map(:symbol) is just like object.send(:symbol),except the :symbol
for list.

But it isn’t. The semantics of the two are completely different. You
can only send a method name, but a naked method name doesn’t make any
sense as an argument to map.

so list.map(:symbol) is good~

Because you don’t understand what’s going on? No thanks.
Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

Hi –

On Tue, 10 Nov 2009, Roger P. wrote:

list.map &:symbol

is less clear than
list.map(:symbol)

I don’t think that &:symbol is inherently clear, but once you know the
rule, then it’s just an example of the rule.

If we were required to write
list.map &:symbol.to_proc

then I would be more easily convinced that list.map(:symbol) is too
implicit. So for me it’s implicit already.

Part of the problem I think is that map(:symbol) wouldn’t really be an
abbreviation of map(&:symbol) (which would presumably still work); it
would be a new semantics for map, namely that map would now take an
argument, but it would look a lot like shorthand for &:symbol. In a
way I wish they were more different, so that they wouldn’t have to be
explained in terms of each other (which I guarantee is how they’ll be
seen).

I’m not too worried about the run time slowdown…so many methods are
already special cased…it doesn’t seem to be a too large concern to the
core guys…

Or, looking at it the other way, maybe there are so many special cases
that we’ve reached the quota :slight_smile: That’s always the flip-side of the
precedent reasoning.

David


The Ruby training with D. Black, G. Brown, J.McAnally
Compleat Jan 22-23, 2010, Tampa, FL
Rubyist http://www.thecompleatrubyist.com

David A. Black/Ruby Power and Light, LLC (http://www.rubypal.com)

list.map(:name)

The next question…

we currently have
list.map => Enumerator

so should
list.map(:method)

best return an array or an enumerator? (though I don’t really understand
why enumerators are cool, apparently they’ve become quite common lately)
Thoughts?

list.map(:method, arg1)

This would be a natural extension of being able to do list.map(:method)

but it feels less intuitive than forcing the user to do

list.map{|i| i.method(arg) }

Or does it?
Thoughts?
-r

Roger P. wrote:
[…]

list.map(:method, arg1)

This would be a natural extension of being able to do list.map(:method)

but it feels less intuitive than forcing the user to do

list.map{|i| i.method(arg) }

Or does it?
Thoughts?

Questions like this point to the very flaws that indicate that list.map
:method is a bad idea. If you understand :symbol.to_proc, or its
abbreviation &:symbol, there is no need for the proposed syntax.

-r

Best,

Marnen Laibow-Koser
http://www.marnen.org
[email protected]

David A. Black wrote:

It would also mean that map (and maybe other Enumerable
methods) had one argument syntax and all other iterators had another,
since presumably there’s no general way to make method(:sym) know when
it’s supposed to actually mean method(&:sym).

As you say, it could be extended to other Enumerable methods which
currently don’t take an argument. Maybe useful for:

students.max(:age)

whereas currently you would have to write something like

students.map(&:age).max

or

students.max { |a,b| a.age <=> b.age }.age

[Note that max(&:age) doesn’t work]

Thinking aloud: methods which iterate over a collection would be
expected to replace each elem with elem.send(*args) if given arguments.
And this could be simplified by delegating that job to the ‘each’
method.

class Array
alias :old_each :each
def each(*args, &blk)
if args.empty?
old_each(&blk)
else
old_each { |elem| yield elem.send(*args) }
end
end
end

module Enumerable
def max(*args, &cmp)
cmp ||= Proc.new { |a,b| a<=>b }
first = true
res = nil
each(*args) do |elem| # << NOTE *args passed down
if first
res = elem
first = false
next
end
res = elem if cmp[elem, res] > 0
end
res
end
end

class Person
attr_accessor :name, :age
def initialize(name, age)
@name, @age = name, age
end
end

students = []
students << Person.new(“Alice”,35)
students << Person.new(“Bob”,33)
puts students.max(:age)

But I don’t feel strongly that this is worthwhile, as IMO the language
is more than complicated enough already.

In any case, it was already decided that the “right” way to pass
arguments to an Enumerable was to create a new Enumerator proxy object
for it. That is, even if ‘each’ did take arguments, you’re supposed to
write

students.to_enum(:each, :age).max

rather than max taking arguments and passing them through.

Roger P. wrote:

we currently have
list.map => Enumerator

so should
list.map(:method)

best return an array or an enumerator?

The ultimate conclusion of this: all Enumerable methods should return
other Enumerators. When you want a real array, then add .to_a to the
end.

(though I don’t really understand
why enumerators are cool, apparently they’ve become quite common lately)

It’s cool because evaluation takes place “left to right” instead of
generating potentially huge intermediate arrays. You start getting
answers sooner, you can write results out to disk or a socket without
buffering, and you can deal with infinite lists, but using the same
syntax.

e.g.
a = (1…1/0.0).select{ |i| i % 2 == 0 }.map{ |i| i + 100
}.take(10).to_a

Implementing this turns out to be easier than you’d think, and is
efficient. No funky Fibers or Continuations required.

http://github.com/trans/facets/blob/master/lib/core/facets/denumerable.rb
http://github.com/trans/facets/blob/master/lib/core/facets/enumerable/defer.rb

Rick Denatale wrote:

On Tue, Nov 10, 2009 at 10:47 AM, Brian C. [email protected]
wrote:

other Enumerators. When you want a real array, then add .to_a to the
end.

And you base this ultimate conclusion on what?

The OP was asking whether map(:foo) should return an Array or an
Enumerator. I was trying to say that if you’re ambivalent about this,
one logical conclusion (or extreme viewpoint) is that you could always
return an Enumerator, even for

map { |x| x*x }

I wasn’t saying that Ruby does anything like this, and it’s a tangent to
the original thrust of map(:foo).

Brian C. wrote:

one logical conclusion (or extreme viewpoint) is that you could always
return an Enumerator, even for

map { |x| x*x }

I wasn’t saying that Ruby does anything like this, and it’s a tangent to
the original thrust of map(:foo).

That’s a pretty yucky option. I use map all the time, and I expect to
get an array back. Adding to_a everywhere is not cool.

Lazy evaluation is cool, though. But it would need a thorough
reconsideration of the language. And considering that Ruby is doing
pretty well without it…