Collection of HOFs
Select Attribute
class Array
def select_attribute attr
self.map { |unit| unit[attr] }
end
end
usage:
[71] pry(main)> [
{:element => 1, :even? => false},
{:element => 2, :even? => true},
{:element => 3, :even? => false}]
.select_attribute :even?
=> [false, true, false]
Collection of patterns
Decorating a collection
Introduction
You want to materialize properties from a collection
Alternative names
- Intermediary
- Functional decorator
- Collection Annotation (from Wallingford’s Roundabout, especially Interface procedure)
Example
[62] pry(main)> [1,2,3]
.map { |x|
{:element => x, :even? => x.even? }
}
=> [{:element=>1, :even?=>false}, {:element=>2, :even?=>true}, {:element=>3, :even?=>false}]
Extra
Naming the decoration
[62] pry(main)> [1,2,3]
.map { |x|
{:element => x, :even? => x.even? }
}
=> [{:element=>1, :even?=>false}, {:element=>2, :even?=>true}, {:element=>3, :even?=>false}]
compare to
[63] pry(main)> [1,2,3]
.map { |x| [x, x.even?] }
=> [[1, false], [2, true], [3, false]]
In the latter, you are expressing the how, not the what.
Expand a HOF
You have a HOF that you want to split in several ones
Example
Original:
[5] pry(main)> [1,2,3]
.select {|x| x.even?}
=> [2]
Introduce an intermediary:
[2] pry(main)> [1,2,3]
.map { |x| [x, x.even?] }
=> [[1, false], [2, true], [3, false]]
Then select all that match:
[67] pry(main)> [1,2,3]
.map { |x| [x, x.even?] }
.select { |x| x[1] }
.map { |x| x.first }
=> [2]
Data accordion
Introduction
The collection suffers the same expansion and contraction than the accordion instrument. In each movement, an action is performed, like a note being played.
Some movements make the collection bigger, decorating it. Others, on the other hand, make it thinner, removing information from it.
Example
Prelude> let numbers = [1..3]
[1, 2, 3] -- each element collection is of size 1
Prelude> map (\n -> (n, even n)) numbers
[(1,False),(2,True),(3,False)] -- each collection is now of size 2
Prelude> filter (\(n,even) -> even) $ map (\n -> (n, even n)) numbers
[(2,True)] -- still size 2
Prelude> map fst $ filter (\(n,even) -> even) $ map (\n -> (n, even n)) numbers
[2] -- size 1 again
If we focus on the size of each element:
- 1
- 2
- 2
- 1
When selecting attributes, the destructuring (Clojure naming system) can be useful:
Prelude> filter (\(_, even) -> even) [(1, False), (2, True), (3, False)]
[(2,True)]
Note: Remember that with filter
the elements are kept or discarded, based on the result of the predicate.
Compact HOF
Introduction
You have several HOFs in a row: you decorate the collection, act on the decorated values, then use only part from the new aggregation.
Example
Diff
- .map { |x| [x, 2 * x]}
- .sort_by { |f| f[1]}
- .map { |x| x.first}
+ .sort_by { |x| 2 * x}
[48] pry(main)> [0, -2, 90, 1, 2, 0]
.map { |x| [x, -x] }
.sort_by { |x| x[1] }
.map { |x| x[0] }
=> [90, 2, 1, 0, 0, -2]
replace it by:
[49] pry(main)> [0, -2, 90, 1, 2, 0]
.sort_by { |x| -x }
=> [90, 2, 1, 0, 0, -2]
Do not use when
This pattern is not appropriate when you depend on (i.e., cannot discard) the data generated by the decoration.
Example:
[50] pry(main)> [0, -2, 90, 1, 2, 0]
.map { |x| [x, -x] }
.sort_by { |x| x[1] }
=> [[90, -90], [2, -2], [1, -1], [0, 0], [0, 0], [-2, 2]]