Using .each with constructors

D

dblack

Hi --

irb(main):001:0> RUBY_VERSION
=> "1.9.0"
irb(main):002:0> a, b, c = 3.times.map { Object.new }
=> [#<Object:0x2382c8>, #<Object:0x2381d8>, #<Object:0x238070>]

Interesting.... That reveals that one of the problems with this magic
enumerator thing is that the method names weren't necessarily chosen
with this in mind, and don't work very well. 3.times.map very
strongly does *not* communicate a 0...3 mapping to me. My first

If you think about it, though, the underlying problem is that 3.times
passes a parameter into the block, which is what makes 3.times.map
{|i| } nonintuitive. The plain 3.times.map {} doesn't suggest a 0...3
mapping, but rather an enumerator consisting of three "passes" which
map naturally turns into a 3-object array.

I'm not getting the "naturally" part :) I mean, of course it's all
non-natural in a sense, but I just can't seem to get my brain to
perceive 3.times as returning something that responds to map this way.

Maybe part of the problem is that it suggests an equivalence between:

[0,1,2].map {}

and

3.times.map {}

which makes it seem like 3.times is return an array. Or something. I
don't even know exactly; it just reads very badly to me.
I predict that this *will* seem intuitive once magic enumerators
have been around a while - note that it already reads perfectly as
3.times.collect

I think that if something has to be around for a while to be
comprehensible, it loses the "intuitive" badge :) I'm sure that
eventually the process of mentally translating it will get faster....
I guess I'm just used to Ruby semantics not having big gaps, and for
me, there's a big gap between "3.times.map" and mapping across 0,1,2.
(I'm not sure about your .collect point; to me that suffers from
exactly the same problem.)

I'm also still troubled by the rather wide net cast by magic
enumerators. I don't think anyone came up with an answer to my
earlier question: When would you ever need this:

some_enumerable.map.other_method

? If the answer is "never", then I don't think map should return an
enumerator.


David

--
http://www.rubypowerandlight.com => Ruby/Rails training & consultancy
http://www.manning.com/black => RUBY FOR RAILS (reviewed on
Slashdot, 7/12/2006!)
http://dablog.rubypal.com => D[avid ]A[. ]B[lack's][ Web]log
(e-mail address removed) => me
 
M

Martin DeMello

I'm not getting the "naturally" part :) I mean, of course it's all
non-natural in a sense, but I just can't seem to get my brain to
perceive 3.times as returning something that responds to map this way.

The way I see it, the notion underlying a "magic enumerator" is that
it returns an object whose 'each' method would perform the same
sequence of yields that the method being magic_enumerated does. If
3.times did a { yield; yield; yield }, mapping over its magic
enumerator would produce the "natural" result

irb(main):001:0> class A
irb(main):002:1> include Enumerable
irb(main):003:1> def each
irb(main):004:2> yield; yield; yield
irb(main):005:2> end
irb(main):006:1> end
=> nil
irb(main):007:0> a = A.new
=> #<A:0x2b575de06ed0>
irb(main):008:0> a.map { Object.new }
Maybe part of the problem is that it suggests an equivalence between:

[0,1,2].map {}

and

3.times.map {}

which makes it seem like 3.times is return an array. Or something. I
don't even know exactly; it just reads very badly to me.

There I agree with you - it's handy, but unaesthetic for 3.times to
yield 0, 1, 2 rather than nil, nil, nil
I think that if something has to be around for a while to be
comprehensible, it loses the "intuitive" badge :) I'm sure that
eventually the process of mentally translating it will get faster....
I guess I'm just used to Ruby semantics not having big gaps, and for
me, there's a big gap between "3.times.map" and mapping across 0,1,2.
(I'm not sure about your .collect point; to me that suffers from
exactly the same problem.)

If I could go back in time and persuade Matz to do things my way,
"map" would be structure-preserving, and "collect" would gather its
results in an array. That's the way the two names read to me, at any
rate. (Then, again, I'd also rename Fixnum#times Fixnum#each and mix
in Enumerable, in which community opinion seems to be very firmly
against me :))
I'm also still troubled by the rather wide net cast by magic
enumerators. I don't think anyone came up with an answer to my
earlier question: When would you ever need this:

some_enumerable.map.other_method

? If the answer is "never", then I don't think map should return an
enumerator.

That is indeed a good point, and 'consistency' is the only thing I can
offer in its favour. It might be a decent optimisation for 'map' to
return self rather than an enumerator.

martin
 
D

dblack

Hi --

Maybe part of the problem is that it suggests an equivalence between:

[0,1,2].map {}

and

3.times.map {}


I completely agree, we could easily monkeypatch Fixnum to deliver

3.numbers => [0, 1, 2]
or
3.numbers( :starting => 2 ) [2, 3, 4 ]
or
3.numbers( :interval => 42 ) => [0, 42, 2*42 ]

I'd rather not "monkeypatch" anything, but you could certainly extend
Fixnum to do that, with the usual caveats about extending core
classes.
I do not like the name, but maybe someone has a better idea?

Your example made me think of upto, and indeed I find this much closer
to being comprehensible than the 'times' version:
=> [0, 10, 20, 30]

It makes some semantic sense to think of "0.upto(3)" returning
something that could be mapped, whereas "3.times" just doesn't sound
right.

Perhaps this all comes down to the need for more nuance and
selectivity in deciding what each iterator returns, rather than having
them all automatically return enumerators.


David

--
http://www.rubypowerandlight.com => Ruby/Rails training & consultancy
http://www.manning.com/black => RUBY FOR RAILS (reviewed on
Slashdot, 7/12/2006!)
http://dablog.rubypal.com => D[avid ]A[. ]B[lack's][ Web]log
(e-mail address removed) => me
 
D

dblack

Hi --

Maybe part of the problem is that it suggests an equivalence between:

[0,1,2].map {}

and

3.times.map {}

which makes it seem like 3.times is return an array. Or something. I
don't even know exactly; it just reads very badly to me.

There I agree with you - it's handy, but unaesthetic for 3.times to
yield 0, 1, 2 rather than nil, nil, nil

Yes: I think you've pinpointed an underlying problem with "times".
It's really "times_with_index", in a sense, but it's just called
"times".

That's probably why the upto version is much clearer to me (see my
last post, in reply to Robert).
That is indeed a good point, and 'consistency' is the only thing I can
offer in its favour. It might be a decent optimisation for 'map' to
return self rather than an enumerator.

There's another kind of consistency, though: consistent attention to
finding what's exactly right for every syntactic and semantic
component of Ruby, one at a time, whether it's just like others with
which it has something in common or not :) I really think it would
be worth hand-crafting these return values based on what each method
actually does.


David

--
http://www.rubypowerandlight.com => Ruby/Rails training & consultancy
http://www.manning.com/black => RUBY FOR RAILS (reviewed on
Slashdot, 7/12/2006!)
http://dablog.rubypal.com => D[avid ]A[. ]B[lack's][ Web]log
(e-mail address removed) => me
 
T

transfire

Perhaps this all comes down to the need for more nuance and
selectivity in deciding what each iterator returns, rather than having
them all automatically return enumerators.

But why does #times return the receiver in the first place? That can;t
possible be anywhere near as useful as the alternative. And indeed 1.9
changes #times to return an enumerator instead. Okay, but why all the
bother? Just return the collection. I'm not against enumerator returns
pre se, but I think it's silly to do fancy things like enumerator when
there's an obvious result in the first place. We rick allowing these
enumerators to become sort of a hackish crutch.

T.
 
J

Jacob Fugal

But why does #times return the receiver in the first place? That can't
possible be anywhere near as useful as the alternative.... Just return
the collection.

The primary reason I can see is that Fixnum#times is frequently used
with a fairly large valued receiver. Such as in benchmarking:

10000.times{ ... }

Do you really want that to generate a 10000 element array? Probably
not. I too can see (and desire) the utility of a returned collection,
but we lose the beauty of Fixnum#times as a replacement for the purely
incremental while loop.

Jacob Fugal
 
T

transfire

Jacob said:
The primary reason I can see is that Fixnum#times is frequently used
with a fairly large valued receiver. Such as in benchmarking:

10000.times{ ... }

Do you really want that to generate a 10000 element array? Probably
not. I too can see (and desire) the utility of a returned collection,
but we lose the beauty of Fixnum#times as a replacement for the purely
incremental while loop.

Ouch. Good point. I like enumerators! ;-)

T.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,431
Messages
2,571,678
Members
48,796
Latest member
Greg L.

Latest Threads

Top