离散联合上的 F# 高阶函数
F# higher-order functions on discrete unions
我正在尝试用 F# 编写一个 REST 客户端,它使用强类型值来定义资源 "signatures"。我决定使用离散联合将签名表示为可选参数列表。我计划提供一种通用方法,将参数值转换为表示将用于创建请求的 key/value 对的元组列表。这是一个学习练习,所以我正在尝试使用 idomatic F#。
我被卡住了,试图定义两个具有相似签名的不同离散联合。有没有办法让我在运行时动态 select 正确的模式匹配函数?
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会判断是调用toParamA还是调用toParamB
我想我已经意识到我原来的方法适用于当前情况。但是,我仍然有兴趣知道我的伪代码是否可行。
我认为最普通的 F# 方式是明确说明您正在构建参数列表的 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
符号是所谓的 "hat type",阅读更多 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, but I believe it is an outdated way to do it. For more inspiration, take a look at these projects: FsControl, FSharpPlus, Higher 中有详细描述。
我正在尝试用 F# 编写一个 REST 客户端,它使用强类型值来定义资源 "signatures"。我决定使用离散联合将签名表示为可选参数列表。我计划提供一种通用方法,将参数值转换为表示将用于创建请求的 key/value 对的元组列表。这是一个学习练习,所以我正在尝试使用 idomatic F#。
我被卡住了,试图定义两个具有相似签名的不同离散联合。有没有办法让我在运行时动态 select 正确的模式匹配函数?
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会判断是调用toParamA还是调用toParamB
我想我已经意识到我原来的方法适用于当前情况。但是,我仍然有兴趣知道我的伪代码是否可行。
我认为最普通的 F# 方式是明确说明您正在构建参数列表的 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
符号是所谓的 "hat type",阅读更多 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, but I believe it is an outdated way to do it. For more inspiration, take a look at these projects: FsControl, FSharpPlus, Higher 中有详细描述。