202107272306 Data-last pipe design
Contrasts with 202107272307 Data-first pipe design.
Background
To start, the basic idea here is that the “data” or “object” is passed as the last parameter to a function. Consider the standard library List.map
function from OCaml.1
let numbers = ;
(* numbers is the "data" and the last parameter *)
let listTwo = map;
Data-last pipe design is common in functional languages because of currying #thread and partial application #thread.
Let’s take the example from above and break it up taking advantage of currying and partial application.1
let addOneToList = map;
let listA = ;
let listB = addOneToList;
It’s obvious in this example why data-last makes sense. Partially applying the function gives us a new function that we can reuse on different data objects. This is a powerful (de)composition #thread mechanism and the style of programming building up composed functions without specifying their parameters is called point-free programming #thread. Point-free programming is only possibly because of currying and partial application.
Functional languages supported currying by default and therefore naturally gravitated towards data-last conventions.
The Pipe (last) Operator |>
The last major reason that data-last was widely adopted was because the pipe operator.1 It was introduced in the theorem proving language written in StandardML called Isabelle and later adopted by others like OCaml, Haskell, F#, or Elm. The value it brought to functional language centers around the verbosity and ergonomics of chaining function calls and their outputs.
(* Without |> we have to nest calls or assign temp variables *)
let getFolderSize = folderName => ;
(* With |> we can chain the output of each function to the next *)
let getFolderSize = folderName =>
folderName
|> filesUnderFolder
|> map
|> map
|> fold
|> bytesToMB;
You can see how this is much cleaner. But this raises another question — why doesn’t this solve the type inference problem? The data is now top-left of the function. The answer is simply that the |>
pipe operator is an infix function #thread. This means the pipe is re-written to a function call and the body of the callback is evaluated before the map
as a whole.
- How does that square with the rest of this data-last causing a problem with inference?
- How does this still cause it to be evaluated wrong? You would literally put the argument first, but for some reason the body of the callback function is evaluated first?
Advantages
- A long tradition in functional languages
- Great integration with partially applied functions
- More straight-forward composition
- A simpler solution for application of functions with optional labeled arguments
- Works with
|>
, which is supported by default on every OCaml backend.
-
Chávarri, J. (2019, May 10). Data-first and data-last: A comparison. Javier Chávarri. https://www.javierchavarri.com/data-first-and-data-last-a-comparison/ ↩ ↩2 ↩3