5

在 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 来支持流水线和部分应用程序?

4

2 回答 2

3

流水线使用部分应用,它只是通过指定参数然后指定函数来调用函数的另一种方式。

myTransitions |> getNewState "A" "one"

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

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

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

let stateMachine a b = getNewState a b myTransitions

于 2013-07-14T11:44:38.713 回答
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

(或者呼叫者总是可以自己做,以防您缺少他们需要的一些签名)

于 2013-07-14T10:32:42.437 回答