| More notes on the “Thinking Functionally” series. Previous notes are @ “[_______1 | > F# – Getting Started, Thinking Functionally](/2015/06/16/___1-f-getting-started-thinking-functionally/)“. |
#6 Partial Application
Breaking down functions into single parameter functions is the mathematically correct way of doing it, but that is not the only reason it is done — it also leads to a very powerful technique called partial function application.
For example:
let add42 = (+) 42 // partial application
add42 1
add42 3
[1;2;3] |> List.map add42
let twoIsLessThan = (<) 2 // partial application twoIsLessThan 1 twoIsLessThan 3 // filter each element with the twoIsLessThan function [1;2;3] |> List.filter twoIsLessThan
let printer = printfn "printing param=%i"
[1;2;3] |> List.iter printer
Each case a partially applied function above it can then be reused in multiple contexts. It can also fix function parameters.
let add1 = (+) 1
let add1ToEach = List.map add1 // fix the "add1" function
add1ToEach [1;2;3;4]
let filterEvens =
List.filter (fun i -> i%2 = 0) // fix the filter function
filterEvens [1;2;3;4]
Then the following shows plug in behavior that is transparent.
let adderWithPluggableLogger logger x y =
logger "x" x
logger "y" y
let result = x + y
logger "x+y" result
result
let consoleLogger argName argValue =
printfn "%s=%A" argName argValue
let addWithConsoleLogger = adderWithPluggableLogger consoleLogger
addWithConsoleLogger 1 2
addWithConsoleLogger 42 99
let popupLogger argName argValue =
let message = sprintf "%s=%A" argName argValue
System.Windows.Forms.MessageBox.Show(
text=message,caption="Logger")
|> ignore
let addWithPopupLogger = adderWithPluggableLogger popupLogger
addWithPopupLogger 1 2
addWithPopupLogger 42 99
Designing Functions for Partial Application
Sample calls to the list library:
List.map (fun i -> i+1) [0;1;2;3]
List.filter (fun i -> i>1) [0;1;2;3]
List.sortBy (fun i -> -i ) [0;1;2;3]
Here are the same examples using partial application:
let eachAdd1 = List.map (fun i -> i+1)
eachAdd1 [0;1;2;3]
let excludeOneOrLess = List.filter (fun i -> i>1)
excludeOneOrLess [0;1;2;3]
let sortDesc = List.sortBy (fun i -> -i)
sortDesc [0;1;2;3]
Commonly accepted guidelines to multi-parameter function design.
- Put earlier: parameters ore likely to be static. The parameters that are most likely to be “fixed” with partial application should be first.
-
Put last: the data structure or collection (or most varying argument). Makes it easier to pipe a structure or collection from function to function. Like:
let result = [1..10] |> List.map (fun i -> i+1) |> List.filter (fun i -> i>5) - For well-known operations such as “subtract”, put in the expected order.
Wrapping BCL Function for Partial Application
Since the data parameter is generally last versus most BCL calls that have the data parameter first, it’s good to wrap the BCL.
let replace oldStr newStr (s:string) =
s.Replace(oldValue=oldStr, newValue=newStr)
let startsWith lookFor (s:string) =
s.StartsWith(lookFor)
Then pipes can be used with the BCL call in the expected way.
let result =
"hello"
|> replace "h" "j"
|> startsWith "j"
["the"; "quick"; "brown"; "fox"]
|> List.filter (startsWith "f")
…or we can use function composition.
let compositeOp = replace "h" "j" >> startsWith "j"
let result = compositeOp "hello"
Understanding the Pipe Function
The pipe function is defined as:
let (|>) x f = f x
It allows us to put the function argument in front of the function instead of after.
let doSomething x y z = x+y+z
doSomething 1 2 3
If the function has multiple parameters, then it appears that the input is the final parameter. Actually what is happening is that the function is partially applied, returning a function that has a single parameter: the input.
let doSomething x y =
let intermediateFn z = x+y+z
intermediateFn // return intermediateFn
let doSomethingPartial = doSomething 1 2
doSomethingPartial 3
3 |> doSomethingPartial
#7 Function Associativity and Composition
Function Associativity
This…
let F x y z = x y z
…means this…
let F x y z = (x y) z
Also three equivalent forms.
let F x y z = x (y z)
let F x y z = y z |> x
let F x y z = x <| y z
Function Composition
Here’s an example
let f (x:int) = float x * 3.0 // f is int->float
let g (x:float) = x > 4.0 // g is float->bool
We can create a new function h that takes the output of “f” and uses it as the input for “g”.
let h (x:int) =
let y = f(x)
g(y) // return output of g
A much more compact way is this:
let h (x:int) = g ( f(x) ) // h is int->bool
//test
h 1
h 2
These are notes, to read more check out the Function Composition.
##
