The long way through Software Craftsmanship

Functional Implementation Patterns

Nov 2, 2015 - 3 minute read - Comments - functionalrubyhigher-order-functionhofpattern

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

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

Self-Study in November 2015 Self-Study in December 2015