是否可以根据具有命名字段的记录编写泛型?

Fla*_*ame 4 f#

我有一些代码有很多重复。

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)

这可能吗?使这项工作有效的唯一方法是将成员函数添加到每个记录类型吗?

Phi*_*ter 9

是的,可以使用 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 可能很棒,但可能会导致对抽象过于满意,有时还会导致编译时速度减慢。不过,像这样明智地使用它并没有那么糟糕。


Tom*_*cek 5

顺便记录一下,您也可以使用普通的面向对象接口来解决这个问题。这与 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)

  • 这就说得通了!我认为很多人对 F# 中的 OO 持谨慎态度 :-) 我认为 OO 的某些方面与 F# 的函数式风格非常自然地协同工作(例如接口),但有些东西是任何人都不应该使用的(甚至F# 之外!)比如复杂的类层次结构。接口的唯一问题是您必须显式地实现它们,但我认为如果它使代码中的意图更加清晰,那么这是值得做的...... (2认同)
  • “仅供记录”……很好。 (2认同)