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

  • Follow


On Mon, Jun 22, 2009 at 2:52 PM, Brian Candler<b.candler@pobox.com> wrote:
> 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]

0
Reply robert.dober (2193) 6/22/2009 7:03:45 PM

Hi --

On Tue, 23 Jun 2009, Robert Dober wrote:

> On Mon, Jun 22, 2009 at 2:52 PM, Brian Candler<b.candler@pobox.com> wrote:
>> 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.

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

0
Reply dblack (1323) 6/22/2009 7:13:11 PM


On Mon, Jun 22, 2009 at 9:13 PM, David A. Black<dblack@rubypal.com> wrote:
> Hi --
>
> On Tue, 23 Jun 2009, Robert Dober wrote:
>
>> On Mon, Jun 22, 2009 at 2:52 PM, Brian Candler<b.candler@pobox.com> wrot=
e:
>>>
>>> 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.joi=
n
>>> on the *whole* collection. From that point of view,
>>>
>>> =A0coll.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.
>
> 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 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:
>
> =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.
>
>> And if the receiver already was an Enumerator I want to call map for
>> some purpose too.
>
> 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

0
Reply robert.dober (2193) 6/22/2009 7:31:38 PM

Robert Dober wrote:
> 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.

-- 
Posted via http://www.ruby-forum.com/.

0
Reply b.candler (2627) 6/22/2009 7:36:39 PM

On Mon, Jun 22, 2009 at 9:36 PM, Brian Candler<b.candler@pobox.com> wrote:
> Robert Dober wrote:
>> 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.
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

0
Reply robert.dober (2193) 6/22/2009 11:15:18 PM

Hi --

On Tue, 23 Jun 2009, Robert Dober wrote:

> On Mon, Jun 22, 2009 at 9:36 PM, Brian Candler<b.candler@pobox.com> wrote:
>> Robert Dober wrote:
>>> 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.
> 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

0
Reply dblack (1323) 6/23/2009 1:32:42 AM

On Tue, Jun 23, 2009 at 3:32 AM, David A. Black<dblack@rubypal.com> wrote:
<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 ;).

>> Can you do this with lazy Enumerators?
>>
>> fibs =3D 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 =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]

0
Reply robert.dober (2193) 6/23/2009 8:45:06 AM

Robert Dober wrote:
> On Tue, Jun 23, 2009 at 3:32 AM, David A. Black<dblack@rubypal.com> 
> wrote:
> <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.
-- 
Posted via http://www.ruby-forum.com/.

0
Reply b.candler (2627) 6/23/2009 2:47:49 PM

On Tue, Jun 23, 2009 at 4:47 PM, Brian Candler<b.candler@pobox.com> wrote:
> Robert Dober wrote:
>> On Tue, Jun 23, 2009 at 3:32 AM, David A. Black<dblack@rubypal.com>
>> wrote:
>> <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?
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
> --
> Posted via http://www.ruby-forum.com/.
>
>



--=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]

0
Reply robert.dober (2193) 6/23/2009 6:11:29 PM

8 Replies
30 Views

(page loaded in 0.05 seconds)


Reply: