离散联合的F#高阶函数

时间:2015-04-09 18:00:59

标签: f#

我正在尝试在F#中编写一个REST客户端,它使用强类型值来定义资源“签名”。我决定使用离散联合将签名表示为可选参数列表。我计划提供一种通用方法将参数值转换为元组列表,这些元组表示将用于创建请求的键/值对。这是一个学习练习所以我试图使用idomatic F#。

我遇到了困难,试图定义两个具有相似签名的不同离散联盟。有没有办法在运行时动态选择正确的模式匹配函数?

type A =
    | First of string
    | Second of string

type B =
    | First of string
    | Second of string
    | Third of string

let createA f s =
    [A.First f; A.Second s;]

let createB f s t =
    [B.First f; B.Second s; B.Third t]

let toParamA elem =
    match elem with
    | A.First f -> "First", f
    | A.Second s -> "Second", s

let toParamB elem =
    match elem with
    | B.First f -> "First", f
    | B.Second s -> "Second", s
    | B.Third t -> "Third", t

let rec toParam f state args =
    match args with
    | [] -> state
    | head::tail -> 
        let state' = (f head)::state
        toParam f state' tail

let argA = createA "one" "two"
let argB = createB "one" "two" "three"

let resultA = argA |> toParam toParamA []
let resultB = argB |> toParam toParamB []

结果目前是正确的,我对API不满意:

val resultA : (string * string) list = [("Second", "two"); ("First", "one")]
val resultB : (string * string) list = [("Third", "three"); ("Second", "two"); ("First", "one")]

更新

问的问题是我希望看起来像什么?

let resultA = argA |> toParam []

然后toParam会想出是否要调用ParamA或toParamB。

我想我已经意识到我的原始方法适用于当前的情况。但是,我仍然有兴趣知道我的伪代码是否可行。

1 个答案:

答案 0 :(得分:1)

我认为最流行的F#-way将明确说明您构建参数列表的API方法:

type ApiArgs = ApiA of A list | ApiB of B list

然后你可以像这样混淆toParamAtoParamB函数:

let toParam = function
| ApiA args ->
    let toParamA = function
    | A.First x -> "First", x
    | A.Second x -> "Second", x

    List.map toParamA args 
| ApiB args ->
    let toParamB = function
    | B.First x -> "First", x
    | B.Second x -> "Second", x
    | B.Third x -> "Third", x

    List.map toParamB args 

我认为这里有两种改进的可能性。首先,代码过于重复和无聊。您可以使用API​​的类型提供程序生成代码,或者在运行时使用反射来进行转换。

其次,将AB转换为(string * string) list的多态行为发生在运行时,但我认为您可以在编译时将其删除:

type X = X with
    static member ($) (_, args : A list) =
        let toParamA = function
        | A.First x -> "First", x
        | A.Second x -> "Second", x

        List.map toParamA args

    static member ($) (_, args : B list) =
        let toParamB = function
        | B.First x -> "First", x
        | B.Second x -> "Second", x
        | B.Third x -> "Third", x

        List.map toParamB args

let inline toParam' args = X $ args

如果您检查toParam'的推断类型,它将类似于:

val inline toParam' :
  args: ^a ->  ^_arg3
    when (X or  ^a) : (static member ( $ ) : X *  ^a ->  ^_arg3)

^a符号是所谓的"帽子类型",阅读更多here

然后使用不同类型的参数调用toParam'会产生正确的结果:

> toParam' (createA "one" "two");;
val it : (string * string) list = [("First", "one"); ("Second", "two")]
> toParam' (createB "1" "2" "3");;
val it : (string * string) list =
  [("First", "1"); ("Second", "2"); ("Third", "3")]
>

这项技术在this blog中有详细描述,但我认为这是一种过时的方法。如需更多灵感,请查看以下项目:FsControlFSharpPlusHigher