Project

General

Profile

Bug #14015

Updated by nobu (Nobuyoshi Nakada) over 6 years ago

The subtle difference between `yield 1, 2` and `yield [1, 2]` has always confused me. 

 Today I wanted to pass a method to Hash#flat_map and realized how it's even more confusing than I thought. 

 I assumed that `Hash#each` was calling `yield key, value`. But somehow it's not that simple: 

 ~~~ ruby 
 {a: 1}.map(&->(key, 1}.map(&->{key, value){}) # => [nil] 
 {a: 1}.flat_map(&->(key, 1}.flat_map(&->{key, value){})    #=> ArgumentError: wrong number of arguments (given 1, expected 2) 
 ~~~ 

 What blows my mind, is that a custom method `each` that does `yield a, 1` has different result! 

 ~~~ ruby 
 class << o = Object.new 
   include Enumerable 
   def each 
     yield :a, 1 
   end 
 end 
 o.map(&->(key, value){})    # => [nil] 
 o.flat_map(&->(key, value){})    # => [nil]    does not raise!! 
 ~~~ 
 I don't even know how that's possible, since Hash doesn't have a specialized `flat_map` method... 

 Here's a list of methods that accept a lambda of arity 2 (as I would expect) 
 For Hash 
   each, any?, map,      select, reject,  
 For a custom yield 
   each, any?, map,      count, find_index,    flat_map, all?, one?, none?, take_while, uniq 

 These two lists have `each`, `map` and `any?`    in common. Others work in one flavor, not the other. Many require arity 1: find, sort_by, grep, grep_v, count, detect, find_index, find_all, ... 

 To make things even more impossible, `Hash#map` has been working with arity 2 since Ruby 2.4 only. 

 Finally, `Hash#each` changes the expected arity of `select`, `reject`, and `any?`, but not of `map`: 

 ~~~ruby 
     {a: 1}           .select(&->(a, b){})    # => {} 
     {a: 1}.each.select(&->(a, b){}) # => wrong number of arguments (given 1, expected 2) 
 ~~~ 

 Conclusion: 

 It seems more or less impossible to guess the expected arity of methods of Enumerable and of Hash, and they are not even consistent with one another. This makes these methods more or less unusable with lambdas. 

 While compatibility could be an issue, the fact that `Hash#map` has changed it's arity (I believe following https://bugs.ruby-lang.org/issues/13391 ) makes me think that compatibility with the lesser used methods would be even less of a problem. 

 My personal wish: that the following methods be fixed to expect arity 2 for lambdas: 

 For both Hash & Enumerable: 
 * find, sort_by, grep, grep_v, detect, find_all, partition, group_by, min_by, max_by, minmax_by, reverse_each, drop_while, sum 
 For Hash: 
 * count, find_index,    flat_map, all?, one?, none?, take_while, uniq 
 For Enumerable: 
 * select, reject 

 Matz, what do you think?

Back