我有一些代码有很多重复。
type RecordA = {
Name: string
// ...
}
type RecordB = {
Name: string
// ...
}
val getTheHandler: (name: string) -> (() -> ())
let handleA (record: RecordA) =
(getTheHandler record.Name) ()
let handleB (record: RecordB) =
(getTheHandler record.Name) ()
Run Code Online (Sandbox Code Playgroud)
我想知道是否可以编写一些通用函数来让我简化/重构getTheHandler record.Name
. 在尝试重构该片段时,编译器希望选择一种记录类型或另一种记录类型。
所以尝试这个,我得到一个编译器错误:
let shorter (record: 'T) =
(getTheHandler record.Name) ()
// later:
shorter myRecordA // FS0001: This expression was expected to have type RecordB but here has type RecordA
Run Code Online (Sandbox Code Playgroud)
这可能吗?使这项工作有效的唯一方法是将成员函数添加到每个记录类型吗?
是的,可以使用 SRTP - 请参阅此处:F# 中的部分匿名记录
这是使用您的用例的示例:
let getTheHandler (name: string) () = printfn $"{name}"
let inline handle (r: ^T) =
(^T : (member Name: string) r)
let ra: RecordA = { Name = "hello" }
let rb: RecordB = { Name = "world" }
handle ra // "hello"
handle rb // "world"
Run Code Online (Sandbox Code Playgroud)
但我还要说,一点点重复也没有那么糟糕。SRTP 可能很棒,但可能会导致对抽象过于满意,有时还会导致编译时速度减慢。不过,像这样明智地使用它并没有那么糟糕。
顺便记录一下,您也可以使用普通的面向对象接口来解决这个问题。这与 F# 中的功能设计配合得很好,而且非常干净。显式实现接口还涉及更多工作,但好处是您最终会得到更清晰的显式代码(并且接口可以比仅仅成员名称更好地模拟意图):
定义和实现接口:
type INamed =
abstract Name : string
type RecordA =
{ Name: string }
interface INamed with
member x.Name = x.Name
type RecordB =
{ Name: string }
interface INamed with
member x.Name = x.Name
Run Code Online (Sandbox Code Playgroud)
要使用这个:
let getTheHandler (name:string) =
fun () -> printfn "Hi %s" name
let handle (record: INamed) =
(getTheHandler record.Name) ()
Run Code Online (Sandbox Code Playgroud)