我正在尝试在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。
我想我已经意识到我的原始方法适用于当前的情况。但是,我仍然有兴趣知道我的伪代码是否可行。
答案 0 :(得分:1)
我认为最流行的F#-way将明确说明您构建参数列表的API方法:
type ApiArgs = ApiA of A list | ApiB of B list
然后你可以像这样混淆toParamA
和toParamB
函数:
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的类型提供程序生成代码,或者在运行时使用反射来进行转换。
其次,将A
或B
转换为(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中有详细描述,但我认为这是一种过时的方法。如需更多灵感,请查看以下项目:FsControl,FSharpPlus,Higher。