流水线与API设计的部分应用

时间:2013-07-14 00:25:16

标签: f# functional-programming pipeline

在F#中,许多采用序列的函数都将序列作为支持流水线操作的最后一个参数。

在设计API时,我可以遵循这种趋势,就像在这个简单的状态机示例中一样:

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

此处getNewState有签名:

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

支持流水线操作:

myTransitions |> getNewState "A" "one"

但在某些情况下,序列是不变的,而其他参数不同。在状态机示例中,将为给定的状态机修复转换表(transitions)。 getNewState将被多次调用,具有不同的状态和事件。如果序列是 first 参数,则调用者可以使用部分应用程序:

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

现在getNewState有签名:

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

stateMachine有签名:

(string -> string -> string option)

如何根据来电者的选择设计API以支持流水线操作和部分应用?

2 个答案:

答案 0 :(得分:3)

Pipelining使用部分应用程序,它只是通过先指定参数然后再指定函数来调用函数的另一种方法。

myTransitions |> getNewState "A" "one"

此处getNewState首先部分应用于获取具有一个参数的函数,然后使用myTransitions调用该函数。

使用具有不同参数顺序但函数名称仍然相同的函数的方法是使用方法重载,即具有静态方法的类型,但随后松开隐式部分应用程序,因为方法将参数作为单个元组。

最好坚持使用一个签名,并且调用者可以根据需要轻松创建另一个具有不同参数顺序的函数。例如,在第二个代码示例中,您可以将第一个示例的getNewState用作:

let stateMachine a b = getNewState a b myTransitions

答案 1 :(得分:2)

为什么转换需要改变它们不是形成状态机的定义?

在任何情况下,如果您感觉到转换位于最后位置但仍希望部分应用的冲动,您可以始终创建一个能够实现此功能的功能:

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

或者您可以制作一般rotateArgs函数并使用它来定义您的特殊签名:

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

(或者,如果您缺少一些他们需要的签名,调用者可以随时为自己执行此操作)