在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将以不同的状态和事件被多次调用。如果序列是第一个参数,则调用者可以使用部分应用程序:
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?

最佳答案

流水线使用部分应用程序,这是通过先指定参数然后再指定函数来调用函数的另一种方法。
myTransitions |> getNewState "A" "one"
在这里,首先将getNewState局部应用以获得具有一个参数的函数,然后使用myTransitions调用该函数。

具有可以具有不同参数顺序但功能名称仍保持不变的函数的方法是使用方法重载,即具有静态方法的类型,但是由于方法将参数视为单个元组,因此您将松散隐式部分应用程序。

最好坚持使用一个签名,并且调用方可以轻松地创建另一个函数,该函数具有所需的不同参数顺序。例如,在第二个代码示例中,可以将第一个示例的getNewState用作:
let stateMachine a b = getNewState a b myTransitions

10-07 16:54