constructor(inputs) |> reducer(...) |> reducer(...) ... |> converter
This is the way the data-centric modules in Elixir are built today. In Designing Elixir Systems with OTP speak, they are the *core* modules, and I believe many Elixir developers build code this way, even if we don't vocalize it in this way.
Building code like this puts us in conflict in one important way:
1. Elixir modules are generally organized around data of a common type (let's say T)
2. Named functions in a module have T as the first argument.
3. Pipes rely on T as a first argument.
4. In Enum.reduce, T is the accumulator, and it's the second argument of the reducer.
3 and 4 are in conflict! The accumulator plays the role of T in the reducer:
reducer(any(), T) :: T
That's backwards, and it won't pipe.
If we have this:
10 |> subtract(4) |> subtract(3)
and we want to express that code over a list that's arbitrarily long, we we must do this:
Enum.reduce([4, 3, ...], 10, fn x, acc -> subtract(acc, x) end)
to flip the two arguments to the correct place.
But we can't do so. I know the Elixir way is to generally put up with a little ceremony for the common good of a tighter standard library, most of the time, but I don't think this problem applies. I think that Elixir *wants* this function:
Enum.fold([4, 3], 10, &subtract2)
To me the cost is high:
1. We must add a function to Enum, a central Elixir function
2. The function does almost the same thing as an existing function.
To me at least the benefit is higher:
Enum.fold encourages us to write better code in three ways:
A, Enum.fold encourages us to write code with T first, the Elixir way.
B, Enum.fold encourages named functions rather than anonymous functions which leads to more opportunities to name concepts without comments, always a good thing.
C. This strategy has a profound impact on tests of reducers, as we can simply pipe them and check the results.
What do you think?
-bt