Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pipelining vs. partial application for API design

In F#, many functions that take sequences have the sequence as the last parameter to support pipelining.

When designing an API, I can follow that trend, as in this simple state machine example:

type Transition =
    { CurrentState : string; TriggeringEvent : string; NewState : string }

let getNewState currentState triggeringEvent transitions =
    let isMatch t =
        t.CurrentState = currentState
        && t.TriggeringEvent = triggeringEvent
    match transitions |> Seq.tryFind isMatch with
    | Some transition -> Some(transition.NewState)
    | None -> None

let myTransitions =
    [ { CurrentState = "A"; TriggeringEvent = "one"; NewState = "B" };
      { CurrentState = "B"; TriggeringEvent = "two"; NewState = "A" } ]

let result = myTransitions |> getNewState "A" "one"

printfn "%A" result

Here getNewState has signature:

(string -> string -> seq<Transition> -> string option)

which supports pipelining:

myTransitions |> getNewState "A" "one"

But in some cases, the sequence is constant while the other arguments vary. In the state machine example, the transition table (transitions) will be fixed for a given state machine. getNewState will be called multiple times with different states and events. If the sequence were the first parameter, callers could use partial application:

let getNewState transitions currentState triggeringEvent =
    // body same as before

let stateMachine = getNewState myTransitions

let result1 = stateMachine "A" "one"
let result2 = stateMachine "B" "two"

printfn "%A" result1
printfn "%A" result2

Now getNewState has signature:

(seq<Transition> -> string -> string -> string option)

and stateMachine has signature:

(string -> string -> string option)

How can I design an API to support both pipelining and partial application, at the caller's option?

like image 935
TrueWill Avatar asked Jul 14 '13 00:07

TrueWill


2 Answers

Pipelining uses partial application, it is just another way to call a function by specifying the parameter first and then the function.

myTransitions |> getNewState "A" "one"

Here getNewState is first partially applied to get a function with one param and then that function is called with myTransitions.

The way to have a function that can have different parameter order but the function name still remains same is using method overloading i.e have a type with static methods but then you loose the implicit partial application because methods takes parameters as a single tuple.

It would be better to stick with one signature and the caller can easily create another function which has different parameter order as required. For example in your second code example you can use the first example's getNewState as:

let stateMachine a b = getNewState a b myTransitions

like image 183
Ankur Avatar answered Sep 30 '22 21:09

Ankur


Why would the transitions need to vary don't they form the very definition of your state machine?

In any case, if you feel the urge that the transitions be at the last place but you still want partially apply, you could always make a function that does just that:

let getNewState currentState triggeringEvent transitions = 
    // your definition from above
let createStateMachine transitions currentState triggeringEvent =
    getNewState currentState triggeringEvent transitions)

or you could make a general rotateArgs function and use it to define your special signature something like this:

let rotateArgs f z x y = f x y z
let createStateMachine = rotateArgs getNewState

(Or the caller can always do it for himself in case you are missing some signature they need)

like image 20
Daniel Fabian Avatar answered Sep 30 '22 20:09

Daniel Fabian