Feature #16435
closedArray#to_proc
Description
The idea is obvious, but I couldn't find it discussed anywhere on tracker before. Please point me at the previous discussions if any.
class Array
def to_proc
proc { |v| v.dig(*self) }
end
end
# Or, alternatively, see about alternatives at the end of proposal:
class Array
def to_proc
proc { |v| v[*self] }
end
end
The implementation seems to provide clean and unambiguous collections indexing in Enumerators:
# Basic objects data, which could be obtained from JSON, CSV, Database...
data = [
{name: 'John', department: {id: 1, title: 'Engineering'}, salary: 1000},
{name: 'Jane', department: {id: 1, title: 'Engineering'}, salary: 1200},
{name: 'Boris', department: {id: 2, title: 'Accounting'}, salary: 800},
{name: 'Alice', department: {id: 3, title: 'Management'}, salary: 1500}
]
data.map(&[:name])
# => ["John", "Jane", "Boris", "Alice"]
data.min_by(&[:salary])
# => {:name=>"Boris", :department=>{:id=>2, :title=>"Accounting"}, :salary=>800}
pp data.group_by(&[:department, :title])
# {"Engineering"=>
# [{:name=>"John",
# :department=>{:id=>1, :title=>"Engineering"},
# :salary=>1000},
# {:name=>"Jane",
# :department=>{:id=>1, :title=>"Engineering"},
# :salary=>1200}],
# "Accounting"=>
# [{:name=>"Boris",
# :department=>{:id=>2, :title=>"Accounting"},
# :salary=>800}],
# "Management"=>
# [{:name=>"Alice",
# :department=>{:id=>3, :title=>"Management"},
# :salary=>1500}]}
# Works with arrays, too:
data.map(&:values).map(&[0])
# => ["John", "Jane", "Boris", "Alice"]
# And with mixes:
data.group_by(&[:department, :title]).values.map(&[0, :name])
# => ["John", "Boris", "Alice"]
Naked structured data seems to be a common enough thing to make working with them easier.
Some prior info:
- Googling it around, I found the idea was first invented back in 2014, and another one in 2015, not sure if it was proposed on the tracker.
- Other proposals for
Array#to_proc
was: to call several methods in sequence 1, 2, and to call method with argument 1, 2, 3, to call several methods in parallel: 1
Honestly, I feel that proposed usage is the most frequently needed.
Also, the readability of the version seems more or less straightforward:
# Existing shortcut, for example:
data.map(&:keys)
# Is equivalent to
data.map { |x| x.keys }
# ^^^^^ -- "just remove this part"
# Proposed shortcut:
data.map(&[:name])
# Is equivalent to
data.map { |x| x[:name] }
# ^^^^^ -- "just remove this part"
dig
or []
alternative implementations
It is up to discussion (if the whole idea holds water) whether dig
should be used or just []
. The dig
version is convenient for nested structures but slightly breaks "equivalency" shown above, and just []
version will allow this:
data.map(&:values).map(&[1..-1])
# => [[{:id=>1, :title=>"Engineering"}, 1000], [{:id=>1, :title=>"Engineering"}, 1200], [{:id=>2, :title=>"Accounting"}, 800], [{:id=>3, :title=>"Management"}, 1500]]
Maybe, for the sake of explainability, "just []
" should be preferred, with digging performed by other means.