Is there a step-by-step process for converting several pipelines into a functional composition?
Side note: Did I even use the term "functional composition" in the right context?
I have the following code:
let purchase qty inventory =
buyFrom inventory qty |> fromInventory
|> reduceBy qty
I wanted to refactor this code to support functional composition.
So I thought I could do this:
let purchase qty inventory =
buyFrom inventory >> fromInventory >> reduceBy qty
This code compiles. However, it appears that I am missing an argument when attempting to invoke the purchase function within the debugger.
Here's my test for the function:
[<Test>]
let ``buy last product``() =
[SomeProduct] |> purchase 1
|> should equal []
NOTE:
I'm still struggling with FP fundamentals and still do not understand the process of converting a pipeline work flow into a functional composition.
I think I understand that functional composition relies on partial application which I think I have a handle on now. Thus, I have reason to believe that my refactored function is missing an argument.
Any guidance on a workflow I can use to get better at functional composition?
Appendix:
type Product =
| SomeProduct
type TransactionResult = { Price:decimal; Inventory:Product list }
(* Functions *)
let priceOf qty product =
match product with
| SomeProduct -> match qty with
| 3 -> decimal (qty - 1) * 1.99m
| _ -> decimal (qty) * 1.99m
let rec reduceBy count list =
match list with
| first::remaining when count = 1 -> remaining
| first::second::remaining when count > 1 -> second::remaining |> reduceBy (count - 1)
| remaining when count = 0 -> remaining
| _ -> []
let buyFrom inventory qty =
{ Price=priceOf qty (List.head inventory); Inventory=inventory }
let fromInventory transactionResult =
transactionResult.Inventory
Collection pipelines are a programming pattern where you organize some computation as a sequence of operations which compose by taking a collection as output of one operation and feeding it into the next. (Common operations are filter, map, and reduce.)
The transformation pipeline takes the data read from source data based on the Source Attributes setting and transforms it to a necessary target format with one or multiple operators.
Pipeline can be used to chain multiple estimators into one. This is useful as there is often a fixed sequence of steps in processing the data, for example feature selection, normalization and classification.
Let's start with a definition: A transducer is a function that takes a reducer function and returns a reducer function. A reducer is a binary function that takes an accumulator and a value and returns an accumulator.
The direct translation to a composed form would be either:
Using "forward" function composition:
let purchase qty inventory = (buyFrom inventory >> fromInventory >> reduceBy qty) qty
Using "backward" function composition (Personally, I'm not sure I like the term "backward" because this is function composition in the traditional mathemetical sense of the term, i.e. f << g = f ∘ g
):
let purchase' qty inventory = reduceBy qty << fromInventory << buyFrom inventory <| qty
As you can perhaps see from the above examples, it's really most valuable when it lets you skip the last argument entirely. In this example, you can't do that because you depend on qty
twice so I'd recommend you stick with piping here.
In general, if you have a function f(x)
and a function g(x)
you can write a composed function h(x) = (f ∘ g)(x)
in the following ways:
let h x = f (g (x))
let h x = x |> g |> f
let h x = f << g <| x
let h x = (g >> f) x
In the second two cases, using function composition, you can omit the x
entirely.
let h = f << g
let h = g >> f
Usually, if you can massage your pipeline expression to have the form
let f x y value = value |> f1 x |> f2 y |> f3
you can refactor it to
let f x y = f1 x >> f2 y >> f3
That's not quite the case here, as your expression begins with a function call (buyFrom inventory qty
), the value of which is then piped to fromInventory
.
In this case, I think you can refactor to
let purchase' inventory qty =
qty |> buyFrom inventory |> fromInventory |> reduceBy qty
but then you can't really get any further because qty
is also needed as an argument to reduceBy
.
I think you might be able to eliminate both occurrences of qty
by using the Reader or State monad, but it's hardly worth the effort.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With