Forum: Ruby-core [ruby-trunk - Feature #7341][Open] Enumerable#associate

Posted by nathan.f77 (Nathan Broadbent) (Guest)
on 2012-11-13 00:30
(Received via mailing list)
Issue #7341 has been reported by nathan.f77 (Nathan Broadbent).

----------------------------------------
Feature #7341: Enumerable#associate
https://bugs.ruby-lang.org/issues/7341

Author: nathan.f77 (Nathan Broadbent)
Status: Open
Priority: Low
Assignee:
Category: lib
Target version: next minor


Jeremy Kemper proposed Enumerable#associate during the discussion in 
#7297, with the following details:

-------------------

Some background:

#4151 proposes an Enumerable#categorize API, but it's complex and hard 
to understand its behavior at a glance.
#7292 proposes an Enumerable#to_h == Hash[...] API, but I don't think of 
association/pairing as explicit coercion, so #to_h feels misfit.

Associate is a simple verb with unsurprising results. It doesn't 
introduce ambiguous "map" naming. You associate an enumerable of keys 
with yielded values.

Some before/after examples:

Before: Hash[ filenames.map { |filename| [ filename, 
download_url(filename) ]}]
After:  filenames.associate { |filename| download_url filename }
# => {"foo.jpg"=>"http://...", ...}

Before: alphabet.each_with_index.each_with_object({}) { |(letter, 
index), hash| hash[letter] = index }
After:  alphabet.each_with_index.associate
# => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "e"=>4, "f"=>5, ...}

Before: keys.each_with_object({}) { |k, hash| hash[k] = self[k] }  # a 
simple Hash#slice
After:  keys.associate { |key| self[key] }
Posted by bitsweat (Jeremy Kemper) (Guest)
on 2012-11-13 16:03
(Received via mailing list)
Issue #7341 has been updated by bitsweat (Jeremy Kemper).


Thanks for posting, Nathan. See https://gist.github.com/4035286 for the 
full pitch and a demonstration implementation.

In short: associating a collection of keys with calculated values should 
be easy to do and the code should reflect the programmer's intent. But 
it's hard for a programmer to discover which API is appropriate to 
achieve this. Hash[] and each_with_object({}) seem unrelated. And using 
these API requires boilerplate code that obscures the programmer's 
intent.


# Must write code to build a Hash[] argument in the format it expects:
# an array of [key, value] pairs. The intent is hidden by unrelated code
# needed to operate the Hash[] method.
Hash[*collection.map { |elem| [elem, calculate(elem)] }]

# This is better. Much less boilerplate code. But the programmer is
# reimplementing association every time: providing a hash and setting 
the
# value for each key in the collection. This is what an *implementation*
# of association looks like. It shouldn't be repeated in our code.
collection.each_with_object({}) { |elem, hash| hash[elem] = 
calculate(elem) }

# Most Rubyists just use this instead. It uses simple, easy-to-discover 
API.
# But it suffers the same issues: it's an *implementation* of 
association
# that's now repeated in our code, blurring its intent. And it forces us 
to
# disrupt chains of enumerable methods and write boilerplate code.
hash = {}
collection.each { |element| hash[element] = calculate(element) }

# Now the code is stating precisely what the programmer wants to 
achieve.
# Associate is easy to find in docs and uses a verb that "rings a bell" 
to
# programmers who need to associate keys with yielded values.
collection.associate { |element| calculate element }


Marc-André Lafortune proposed a similar Enumerable#associate in #4151. 
The basic behavior is the same, so I consider that a point in favor of 
this method name. It associates values with the enumerated keys. He 
introduces additional collision handling that I consider out of scope. 
For more complex scenarios, using more verbose, powerful API like 
#inject, #each_with_object, or #map + #associate feels appropriate.
----------------------------------------
Feature #7341: Enumerable#associate
https://bugs.ruby-lang.org/issues/7341#change-32864

Author: nathan.f77 (Nathan Broadbent)
Status: Open
Priority: Low
Assignee:
Category: lib
Target version: next minor


Jeremy Kemper proposed Enumerable#associate during the discussion in 
#7297, with the following details:

-------------------

Some background:

#4151 proposes an Enumerable#categorize API, but it's complex and hard 
to understand its behavior at a glance.
#7292 proposes an Enumerable#to_h == Hash[...] API, but I don't think of 
association/pairing as explicit coercion, so #to_h feels misfit.

Associate is a simple verb with unsurprising results. It doesn't 
introduce ambiguous "map" naming. You associate an enumerable of keys 
with yielded values.

Some before/after examples:

Before: Hash[ filenames.map { |filename| [ filename, 
download_url(filename) ]}]
After:  filenames.associate { |filename| download_url filename }
# => {"foo.jpg"=>"http://...", ...}

Before: alphabet.each_with_index.each_with_object({}) { |(letter, 
index), hash| hash[letter] = index }
After:  alphabet.each_with_index.associate
# => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "e"=>4, "f"=>5, ...}

Before: keys.each_with_object({}) { |k, hash| hash[k] = self[k] }  # a 
simple Hash#slice
After:  keys.associate { |key| self[key] }
Posted by marcandre (Marc-Andre Lafortune) (Guest)
on 2012-11-13 17:13
(Received via mailing list)
Issue #7341 has been updated by marcandre (Marc-Andre Lafortune).

Category changed from lib to core
Priority changed from Low to Normal

Hi,

bitsweat (Jeremy Kemper) wrote:
> In short: associating a collection of keys with calculated values should be easy 
to do and the code should reflect the programmer's intent.

A strong +1 from me

> See  https://gist.github.com/4035286

A good start. I'd make one important change: return an enumerator when 
no block is given. Here's why:

1) The form you suggest would be redundant with `Enumerable#to_h`

2) It would be more powerful, for example to associate things that need 
an index...

    rng.each_with_index.associate {|elem, index| ....} # => { [elem, 
index] => ... }, not what you want
    # Easy this form:
    rng.associate.with_index {|elem, index| ... }  # => { elem => ... }

3) Consistency with modern methods dealing with enumerable.


----------------------------------------
Feature #7341: Enumerable#associate
https://bugs.ruby-lang.org/issues/7341#change-32869

Author: nathan.f77 (Nathan Broadbent)
Status: Open
Priority: Normal
Assignee:
Category: core
Target version: next minor


Jeremy Kemper proposed Enumerable#associate during the discussion in 
#7297, with the following details:

-------------------

Some background:

#4151 proposes an Enumerable#categorize API, but it's complex and hard 
to understand its behavior at a glance.
#7292 proposes an Enumerable#to_h == Hash[...] API, but I don't think of 
association/pairing as explicit coercion, so #to_h feels misfit.

Associate is a simple verb with unsurprising results. It doesn't 
introduce ambiguous "map" naming. You associate an enumerable of keys 
with yielded values.

Some before/after examples:

Before: Hash[ filenames.map { |filename| [ filename, 
download_url(filename) ]}]
After:  filenames.associate { |filename| download_url filename }
# => {"foo.jpg"=>"http://...", ...}

Before: alphabet.each_with_index.each_with_object({}) { |(letter, 
index), hash| hash[letter] = index }
After:  alphabet.each_with_index.associate
# => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "e"=>4, "f"=>5, ...}

Before: keys.each_with_object({}) { |k, hash| hash[k] = self[k] }  # a 
simple Hash#slice
After:  keys.associate { |key| self[key] }
Posted by Nathan Broadbent (Guest)
on 2012-11-13 20:45
(Received via mailing list)
>
> 1) The form you suggest would be redundant with `Enumerable#to_h`
>

I agree that 'Enumerable#to_h' would seem more appropriate than the
block-less version of 'associate'. To me, the 'associate' verb implies 
that
the programmer will provide some logic to determine how the elements 
will
be associated. So I also feel that invocation without a block should 
return
an enumerator.

However, if 'to_h' is rejected and 'associate' is all we have to work 
with,
then it would probably be more useful to make 'associate' 
'multi-purpose'
in the way that is currently proposed.
Posted by boris_stitnicky (Boris Stitnicky) (Guest)
on 2012-11-18 05:34
(Received via mailing list)
Issue #7341 has been updated by boris_stitnicky (Boris Stitnicky).


Agree with Marc-Andre.
----------------------------------------
Feature #7341: Enumerable#associate
https://bugs.ruby-lang.org/issues/7341#change-33049

Author: nathan.f77 (Nathan Broadbent)
Status: Open
Priority: Normal
Assignee:
Category: core
Target version: next minor


Jeremy Kemper proposed Enumerable#associate during the discussion in 
#7297, with the following details:

-------------------

Some background:

#4151 proposes an Enumerable#categorize API, but it's complex and hard 
to understand its behavior at a glance.
#7292 proposes an Enumerable#to_h == Hash[...] API, but I don't think of 
association/pairing as explicit coercion, so #to_h feels misfit.

Associate is a simple verb with unsurprising results. It doesn't 
introduce ambiguous "map" naming. You associate an enumerable of keys 
with yielded values.

Some before/after examples:

Before: Hash[ filenames.map { |filename| [ filename, 
download_url(filename) ]}]
After:  filenames.associate { |filename| download_url filename }
# => {"foo.jpg"=>"http://...", ...}

Before: alphabet.each_with_index.each_with_object({}) { |(letter, 
index), hash| hash[letter] = index }
After:  alphabet.each_with_index.associate
# => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "e"=>4, "f"=>5, ...}

Before: keys.each_with_object({}) { |k, hash| hash[k] = self[k] }  # a 
simple Hash#slice
After:  keys.associate { |key| self[key] }
Posted by Thomas Sawyer (7rans)
on 2012-11-18 12:36
(Received via mailing list)
Issue #7341 has been updated by trans (Thomas Sawyer).


=begin
One problem I have with this is the terminology. The term "associate" 
already applies to arrays. ((*Associative arrays*)) are arrays of arrays 
where the first element of an inner array acts a key for the rest.

    [[:a,1],[:b,2]].assoc(:a)  #=> [:a,1]

For this reason I would expect an #associate method to take a flat array 
and group the elements together.

    [:a,1,:b,2].associate  #=> [[:a,1],[:b,2]]

An argument could determine the number elements in each group, the 
default being 2.

Since Hash#to_a returns an associative array, to me it makes sense that 
Array#to_h would reverse the process.

    {:a=>1,:b=>2}.to_a    #=> [[:a,1],[:b,2]]
    [[:a,1],[:b,2]].to_h  #=> {:a=>1,:b=>2}

Putting the two together, your version of associate is easy enough to 
achieve:

    [:a,1,:b,2].associate.to_h

As it turns out, with the exception of the default argument, #associate 
is same as #each_slice. But I think it would be nice to have #associate 
around for it's default and the fact that it reads better in these 
cases.

=end
----------------------------------------
Feature #7341: Enumerable#associate
https://bugs.ruby-lang.org/issues/7341#change-33053

Author: nathan.f77 (Nathan Broadbent)
Status: Open
Priority: Normal
Assignee:
Category: core
Target version: next minor


Jeremy Kemper proposed Enumerable#associate during the discussion in 
#7297, with the following details:

-------------------

Some background:

#4151 proposes an Enumerable#categorize API, but it's complex and hard 
to understand its behavior at a glance.
#7292 proposes an Enumerable#to_h == Hash[...] API, but I don't think of 
association/pairing as explicit coercion, so #to_h feels misfit.

Associate is a simple verb with unsurprising results. It doesn't 
introduce ambiguous "map" naming. You associate an enumerable of keys 
with yielded values.

Some before/after examples:

Before: Hash[ filenames.map { |filename| [ filename, 
download_url(filename) ]}]
After:  filenames.associate { |filename| download_url filename }
# => {"foo.jpg"=>"http://...", ...}

Before: alphabet.each_with_index.each_with_object({}) { |(letter, 
index), hash| hash[letter] = index }
After:  alphabet.each_with_index.associate
# => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "e"=>4, "f"=>5, ...}

Before: keys.each_with_object({}) { |k, hash| hash[k] = self[k] }  # a 
simple Hash#slice
After:  keys.associate { |key| self[key] }
Posted by boris_stitnicky (Boris Stitnicky) (Guest)
on 2012-11-19 22:55
(Received via mailing list)
Issue #7341 has been updated by boris_stitnicky (Boris Stitnicky).


@Tom: Associative arrays are nice, but they are just arrays. No need to 
pamper them too much in the core.
----------------------------------------
Feature #7341: Enumerable#associate
https://bugs.ruby-lang.org/issues/7341#change-33130

Author: nathan.f77 (Nathan Broadbent)
Status: Open
Priority: Normal
Assignee:
Category: core
Target version: next minor


Jeremy Kemper proposed Enumerable#associate during the discussion in 
#7297, with the following details:

-------------------

Some background:

#4151 proposes an Enumerable#categorize API, but it's complex and hard 
to understand its behavior at a glance.
#7292 proposes an Enumerable#to_h == Hash[...] API, but I don't think of 
association/pairing as explicit coercion, so #to_h feels misfit.

Associate is a simple verb with unsurprising results. It doesn't 
introduce ambiguous "map" naming. You associate an enumerable of keys 
with yielded values.

Some before/after examples:

Before: Hash[ filenames.map { |filename| [ filename, 
download_url(filename) ]}]
After:  filenames.associate { |filename| download_url filename }
# => {"foo.jpg"=>"http://...", ...}

Before: alphabet.each_with_index.each_with_object({}) { |(letter, 
index), hash| hash[letter] = index }
After:  alphabet.each_with_index.associate
# => {"a"=>0, "b"=>1, "c"=>2, "d"=>3, "e"=>4, "f"=>5, ...}

Before: keys.each_with_object({}) { |k, hash| hash[k] = self[k] }  # a 
simple Hash#slice
After:  keys.associate { |key| self[key] }
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.