map shall not return an Enumerator ( was re guru help )

R

Robert Dober

Robert Dober wrote:
That I dislike very much. What you want is to run a 'join' operation on
*each member* of the collection, but that looks like running a .map.join
on the *whole* collection. From that point of view,

coll.map { |c| c.join(",") }

expresses very clearly what you're doing.

I agree with you, that this is confusing at first sight, but actually
coll.map.join instead of coll.join does not make any sense at all.
My corollary is:
Any method sent to map makes only sense to be sent to the elements of
the collection and not
to the collection itself because that would make map a NOP.

I believe that the confusion arises from the fact that map returns an
Enumerator and that just seems quite flawed at second thought (or is
this third thought ;).

Why the heck does map return an Enumerator? If I wanted that I surely
would have called to_enum !
And if the receiver already was an Enumerator I want to call map for
some purpose too.

Strange that this has never occurred to me, alhough I always had this
flawed feeling about #map

This is a very strong opinion but it is hold, how does Rick say?, loosely ;=
)

Cheers
Robert

--=20
Toutes les grandes personnes ont d=92abord =E9t=E9 des enfants, mais peu
d=92entre elles s=92en souviennent.

All adults have been children first, but not many remember.

[Antoine de Saint-Exup=E9ry]
 
D

David A. Black

Hi --

I agree with you, that this is confusing at first sight, but actually
coll.map.join instead of coll.join does not make any sense at all.
My corollary is:
Any method sent to map makes only sense to be sent to the elements of
the collection and not
to the collection itself because that would make map a NOP.

You don't send a message to map, though. map is a method; messages go
to objects. Methods can be provided with code blocks, but that's part
of the method call. Once the next dot appears, the method call is over
and the message goes to the resulting object.
I believe that the confusion arises from the fact that map returns an
Enumerator and that just seems quite flawed at second thought (or is
this third thought ;).

Why the heck does map return an Enumerator? If I wanted that I surely
would have called to_enum !

I can't think of any real use case for map returning an enumerator.
It's true that you can do:

array.map.with_index {|e,i| ... }

but that's because enumerators have a with_index method (one of the
very few methods they have that aren't from Enumerable). So you'd be
able to do that no matter how you got the enumerator.

The returning of an enumerator is more useful with certain other
methods. For example:

e = array.each_cons(2)

Now if you iterate over e, you'll get the each_cons(2) behavior.
And if the receiver already was an Enumerator I want to call map for
some purpose too.

You can do that:

array.each_cons(2).map {|one,two| ... }

or whatever. (Is that what you meant?)


David

--
David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Now available: The Well-Grounded Rubyist (http://manning.com/black2)
"Ruby 1.9: What You Need To Know" Envycasts with David A. Black
http://www.envycasts.com
 
R

Robert Dober

Hi --

You don't send a message to map, though. map is a method; messages go
to objects. Methods can be provided with code blocks, but that's part
of the method call. Once the next dot appears, the method call is over
and the message goes to the resulting object.
You are very harsh with me, I believed this was clear in the context ;)
I can't think of any real use case for map returning an enumerator.
It's true that you can do:

=A0array.map.with_index {|e,i| ... }

but that's because enumerators have a with_index method (one of the
very few methods they have that aren't from Enumerable). So you'd be
able to do that no matter how you got the enumerator.
yeah this is an edge case, but why should I do map.with_index when I
mean to_enum.with_index?
The returning of an enumerator is more useful with certain other
methods. For example:
Oh yeah let me be clear I am only speaking about #map.
=A0e =3D array.each_cons(2)

Now if you iterate over e, you'll get the each_cons(2) behavior.


You can do that:

=A0array.each_cons(2).map {|one,two| ... }

or whatever. (Is that what you meant?)
No not at all ;)
I do not necessarily believe that map should return a Proxy to do
magic dot. It really seems people hate it.

coll.map

should just complain about the missing block, or about the missing
message params if one wants to write code
like this
map( :+, 42 )
But why overload the name?

enum.forward( :+, 42)
enum.forward.succ
enum.forward( :succ )

enum.send_all( :+, 42 )
enum.send_all.succ
enum.send_all( :join, ", " )


enum.send_to_elements( :join, "," ) # Wow this is long but very
clear, and automatic code completion will do
# the rest ;)

Ouch this was long :(
Robert
 
B

Brian Candler

Robert said:
I believe that the confusion arises from the fact that map returns an
Enumerator and that just seems quite flawed at second thought (or is
this third thought ;).

Why the heck does map return an Enumerator? If I wanted that I surely
would have called to_enum !

I agree that map and select returning an Enumerator, in the way they do
in 1.8.7/1.9, is pretty pointless. But if map without a block (and
select without a block etc) are not useful, but I don't think it helps
to overload them in the way you want either. I'd rather get an error
raised, as per 1.8.6.

Aside: what's more interesting to me is "horizontal" execution of
enumerators - that is, passing each value along instead of building
intermediate arrays - and thus being able to run map/select on infinite
lists. See:

http://redmine.ruby-lang.org/issues/show/708
http://redmine.ruby-lang.org/issues/show/707

There's also an implementation of this in the facets library.

In this case, if you write

infinite.map { |x| x*2 }.select { |x| x % 3 == 0 } ...

then an Enumerator is returned at each stage of the chain. However you
still need to provide a block to map and a block to select, of course,
so it's not the same as #map without block returning an Enumerator.
 
R

Robert Dober

I agree that map and select returning an Enumerator, in the way they do
in 1.8.7/1.9, is pretty pointless. But if map without a block (and
select without a block etc) are not useful, but I don't think it helps
to overload them in the way you want either. I'd rather get an error
raised, as per 1.8.6.
Reading your mail and David's I came to the same conclusion. I wonder
what took me so long to name
a method that sends a message to the elements of a collection
#send_to_elements ?
Aside: what's more interesting to me is "horizontal" execution of
enumerators - that is, passing each value along instead of building
intermediate arrays - and thus being able to run map/select on infinite
lists. See:

http://redmine.ruby-lang.org/issues/show/708
http://redmine.ruby-lang.org/issues/show/707

There's also an implementation of this in the facets library.

In this case, if you write

=A0infinite.map { |x| x*2 }.select { |x| x % 3 =3D=3D 0 } ...

then an Enumerator is returned at each stage of the chain. However you
still need to provide a block to map and a block to select, of course,
so it's not the same as #map without block returning an Enumerator.
Right now I find it a little confusing to use Enumerators in that way.
I prefer streams to implement lazy data structures, because map with a
block should return an array (or hash, but no argument on this, I am
with the majority on this one ;).
The "the tail of a stream is always a stream" paradigm of streams
makes things so easy to understand.
Anyway if it be streams or enumerators, I am sure that the
introduction to laziness into Ruby would bring great benefits to its
already very concise programming style.
I wonder however if streams are not more general? They are very easy
to be treated as enumerables (as long as one respects the infinity
constraint) or Enumerators.

Can you do this with lazy Enumerators?

fibs =3D cons_stream( 0 ){ cons_stream( 1 ){ add_streams(fibs, fibs.tail) =
} }
if you are interested:
http://ruby-smalltalk.blogspot.com/2009/01/streams-lazy-programs-for-lazy.h=
tml#streams_in_ruby
or James' blog about High Order Ruby
http://blog.grayproductions.net/categories/higherorder_ruby.

Well I guess I got OT on my own thread LOL.
Cheers
Robert
 
D

David A. Black

Hi --

Reading your mail and David's I came to the same conclusion. I wonder
what took me so long to name
a method that sends a message to the elements of a collection
#send_to_elements ?

I'm not sure what that buys you, though. There's already #map and
#send, and between those can't you easily do all of this?
I prefer streams to implement lazy data structures, because map with a
block should return an array (or hash, but no argument on this, I am
with the majority on this one ;).
The "the tail of a stream is always a stream" paradigm of streams
makes things so easy to understand.
Anyway if it be streams or enumerators, I am sure that the
introduction to laziness into Ruby would bring great benefits to its
already very concise programming style.
I wonder however if streams are not more general? They are very easy
to be treated as enumerables (as long as one respects the infinity
constraint) or Enumerators.

Can you do this with lazy Enumerators?

fibs = cons_stream( 0 ){ cons_stream( 1 ){ add_streams(fibs, fibs.tail) } }

I can't do it that compactly (though maybe someone can). Here's some
doodling with the block form of an enumerator:

fib = Enumerator.new do |y|
# y is a "yielder" object -- lazy yielding via <<
y << 1

a,b = 0,1
loop do
y << a + b
a,b = b, a + b
end
end

p fib.next
p fib.next
p fib.next
p fib.next
p fib.next

# Output:
1
1
2
3
5

Or maybe something using cycle.


David

--
David A. Black / Ruby Power and Light, LLC
Ruby/Rails consulting & training: http://www.rubypal.com
Now available: The Well-Grounded Rubyist (http://manning.com/black2)
"Ruby 1.9: What You Need To Know" Envycasts with David A. Black
http://www.envycasts.com
 
R

Robert Dober

<snip>>
It would buy me
coll.send_to_elements( :+ , 42 )
coll.send_to_elements.join( ", ")
versus
coll.map{ |x| x + 42 }
coll.map{ |x| x.join( ", ") }
it is not so much about typing (although nobody would forbid aliasing
#send_to_elements) but about readability.

But maybe I am reading too much about Clojure lately ;).
I can't do it that compactly (though maybe someone can). Here's some
doodling with the block form of an enumerator:

fib =3D Enumerator.new do |y|
# y is a "yielder" object -- lazy yielding via <<
=A0y << 1

=A0a,b =3D 0,1
=A0loop do
=A0 =A0y << a + b
=A0 =A0a,b =3D b, a + b
=A0end
end

p fib.next
p fib.next
p fib.next
p fib.next
p fib.next

# Output:
1
1
2
3
5

Or maybe something using cycle.

Wouldn't Kernel#cons_stream be great for this expressiveness? BTW it
does not necessarily mean that Enumerators should not be lazy, they
could be the foundation of the "functional" interface of streams as I
have of course a class hidden behind my "functional" implementation of
streams.

Cheers
Robert
--=20
Toutes les grandes personnes ont d=92abord =E9t=E9 des enfants, mais peu
d=92entre elles s=92en souviennent.

All adults have been children first, but not many remember.

[Antoine de Saint-Exup=E9ry]
 
B

Brian Candler

Robert said:
<snip>>
It would buy me
coll.send_to_elements( :+ , 42 )
coll.send_to_elements.join( ", ")
versus
coll.map{ |x| x + 42 }
coll.map{ |x| x.join( ", ") }
it is not so much about typing (although nobody would forbid aliasing
#send_to_elements) but about readability.

The first I find pretty much unreadable. Send a message to each element,
but do what with the result? Is it like inject perhaps?

The second is obvious. Iterate over the collection, mapping each element
|x| to x + 42 (or whatever), and storing in a new collection.

Anyway, nobody stops you defining Enumerable#send_to_elements if you
like it, without having to inflict it on the rest of us.
 
R

Robert Dober

The first I find pretty much unreadable. Send a message to each element,
but do what with the result? Is it like inject perhaps?
That is indeed a good point, I just realized that the praise I had for
lazy Enumerators was kind of premature, because it fails to return
lazy Enumerators. That brings me back to what seems the main thing I
have learnt from
our discussion
(i) do not overload a method too much, e.g. enum.map --> Enumerator
(ii) it is not enough to clarify the behavior of a method if the
return value is kind of arbitrary e.g. I want to have arrays or
Enumerators as return values, I did not really think about it, very
baaaad ;).
(iii) do not mix lazy with not lazy (but this is mainly (i)).
The second is obvious. Iterate over the collection, mapping each element
|x| to x + 42 (or whatever), and storing in a new collection.

Anyway, nobody stops you defining Enumerable#send_to_elements if you
like it, without having to inflict it on the rest of us.
Not only do I not have to inflict it on you, I do not even want it to
inflict it on anybody. However sometimes ideas are taken up the
community and become a force of themselves. I have however learnt that
it is much more likely to get "humbled" down. But that is exactly
where one can learn the most.

In this case the exchange with you has made me rethink my whole view
of how I look at Enumerables, Enumerators and Streams. It is very
challenging for me to come up with something that makes sense. Your
opinion of today, might be my opinion of tomorrow.
Now I will go back to look at Smalltalk, I have the feeling that there
is something to learn from their collections ;).

Cheers
Robert


--=20
Toutes les grandes personnes ont d=92abord =E9t=E9 des enfants, mais peu
d=92entre elles s=92en souviennent.

All adults have been children first, but not many remember.

[Antoine de Saint-Exup=E9ry]
 

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

Similar Threads


Members online

Forum statistics

Threads
473,754
Messages
2,569,525
Members
44,997
Latest member
mileyka

Latest Threads

Top