【问题标题】:Is it possible to write a generic based on a record with a named field?是否可以基于具有命名字段的记录编写泛型?
【发布时间】:2021-10-12 15:46:44
【问题描述】:

我有一些重复的代码。

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) ()

我想知道是否可以编写一些通用函数来简化/重构getTheHandler record.Name。在尝试重构 sn-p 时,编译器希望选择另一种记录类型。

所以尝试这个,我得到一个编译器错误:

let shorter (record: 'T) =
   (getTheHandler record.Name) ()

// later:
shorter myRecordA // FS0001: This expression was expected to have type RecordB but here has type RecordA
  

这可能吗?为每个记录类型添加成员函数是唯一的方法吗?

【问题讨论】:

    标签: f#


    【解决方案1】:

    是的,可以使用 SRTP - 请参阅此处:Partial anonymous record in 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"
    

    不过,我还要说,一点点重复也没有那么糟糕。 SRTP 可能很棒,但可能会导致对抽象过于满意,有时还会导致编译时变慢。像这样明智地使用它并没有那么糟糕。

    【讨论】:

      【解决方案2】:

      仅作记录,你也可以使用普通的面向对象接口来解决这个问题。这在 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
      

      要使用这个:

      let getTheHandler (name:string) = 
        fun () -> printfn "Hi %s" name
      
      let handle (record: INamed) =
        (getTheHandler record.Name) ()
      

      【讨论】:

      • 谢谢托马斯。我对使用这样的构造进行了很多思考,但犹豫了,因为它们感觉不那么“fsharp-y”。
      • 有道理!我认为很多人倾向于对 F# 中的 OO 持谨慎态度 :-) 我认为 OO 的某些方面与 F# 的函数式风格非常自然地工作——比如接口——然后有些东西是任何人都不应该使用的(甚至在 F# 之外!),例如复杂的类层次结构。接口的唯一问题是您必须显式地实现它们,但我认为如果它使您的代码中的意图更加清晰,那是值得的......
      • “仅作记录”...很好。
      【解决方案3】:

      您也可以使用高阶函数来解决这个问题。通过函数进行选择。它将变得通用。

      type RecordA = {
          Name: string
      }
      
      type RecordB = {
          Name: string
      }
      
      let getTheHandler name = printfn $"{name}"
      
      let handle chooser record =
          fun () -> getTheHandler (chooser record)
      
      let ra : RecordA = {Name="Hello"}
      let rb : RecordB = {Name="World"}
      
      let name1 = ra |> handle (fun r -> r.Name)
      let name2 = rb |> handle (fun r -> r.Name)
      
      name1 ()
      name2 ()
      
      

      【讨论】:

        猜你喜欢
        • 2021-07-28
        • 2011-09-15
        • 2011-01-02
        • 1970-01-01
        • 2023-04-10
        • 2020-05-11
        • 1970-01-01
        • 2014-07-08
        • 2015-04-11
        相关资源
        最近更新 更多