【问题标题】:Generic method on record记录在案的通用方法
【发布时间】:2016-01-28 00:10:12
【问题描述】:

我想知道是否有更好的方法来实现接受记录并修改它们的函数。

所以我有两种类型的实体,在磁盘上都有对应的文件:

type Picture = { Artist : string; File : string }
type Book = { Author : string; File : string }

我想要可以复制图片和书籍的通用功能。在 OOP 世界中,我可能会创建公共接口 IArtefact { File : string },在两条记录中实现它,然后创建适用于它的 Move 方法。比如:

let move<'a:IArtefact>(a : 'a) (path : string) =
    File.Move(a.File, path)
    { a with File = path }

但是我认为 F# 不支持这样的概念。这样做的 F# 方式是什么?

【问题讨论】:

  • 我认为 F# 不支持任何重要的 C# 功能。支持接口和泛型。这是否是在 F# 中解决问题的好方法并不真正属于 StackOverflow 的范围 - 也许您会在 CodeReview 或 Programmers 获得更多帮助。
  • 据我所知,没有办法定义实现接口的记录,然后在F#中将此记录转换为该接口。这是非常重要的“C# 特性”。但是我可能是错的,或者存在其他一些这样做的方法。因此,恕我直言,在 SO 上提出此类问题是完全可以的。这不是代码审查,也不是一般的编程问题。
  • 为什么不定义如下函数呢? let move (file : string) (path : string)?然后您可以同时使用aPicture.FileaBook.File 调用它。
  • 另见现有问题F# how to pass equivalent of interface 及其答案。
  • 哦,我现在知道你想返回值了......我建议不要这样做,因为它不仅不纯,而且还违反命令查询分离,所以很快就会变得困难推理。

标签: f#


【解决方案1】:

这是可能的,为什么不呢;)

type IArtefact = 
    abstract File: string

type Picture = 
    { Artist : string; File : string }
    interface IArtefact with
        member this.File = this.File

let p = { Artist = "test"; File = "test2" }
(p :> IArtefact).File

编辑:如果你想处理更新:

type IArtefact = 
    abstract File: string
    abstract WithFile: string -> IArtefact

type Picture = 
    { Artist : string; File : string }
    interface IArtefact with
        member this.File = this.File
        member this.WithFile(file) = { this with File = file } :> IArtefact

【讨论】:

  • 我仍然不清楚如何定义返回带有更新文件字段的新工件的方法...let m&lt;'a when 'a :&gt; IArtefact&gt;(p : 'a) = { p with File = "new" };; 似乎很接近但无法编译...
  • 当您只知道接口实现时,您无法创建任意对象的“副本”。接口背后的实际对象可以是任何东西,你不可能知道如何构造另一个相同类型的对象。
  • 我明白这一点,不寻求这样的解决方案。我对 F# 是否有方法处理其记录感兴趣,仅此而已。理想情况下,我很乐意写let m&lt;'a when 'a : record &gt;(q:'a):'a = { q },就是这样。
  • @AlexAtNet:不,没有办法做到这一点 - 当您将记录转换为接口类型时,您“放弃”了记录语法的所有细节。您需要添加一个接口成员来处理更新。
【解决方案2】:

虽然没有通用的更改复制记录的方法,但有一种方法可以移动具有File 的任何内容:

let inline move from where : string =
    let oldFile = (^T : (member File : string) from)
    do() // move here
    where

type Picture = { Artist: string; File: string }
type Book = { Author: string; File: string }

let p = { Artist = "Vincent van Gogh"; File = "Rooftops" }
let p' = { p with File = move p "The Potato Eaters" }

let b = { Author = "Don Syme"; File = "Generics in CLR" }
let b' = { b with File = move b "Expert F#" }

然后可以扩展它以移动任何知道如何移动的东西:

let inline move' a where =
    let oldFile = (^T : (member File : string) a)
    do() // move here
    (^T: (member moved : string -> ^T) a, where)

type Picture' =
    { Artist: string; File: string } with
    member this.moved where = { this with File = where }

type Book' =
    { Author: string; File: string } with
    member this.moved where = { this with File = where }

let p2 = { Artist = "Vincent van Gogh"; File = "Rooftops" }
let p2' = move' p2 "The Potato Eaters"

let b2 = { Author = "Don Syme"; File = "Generics in CLR" }
let b2' = move' b2 "Expert F#"

【讨论】:

  • 这看起来可以接受,非常感谢。我想我会选择这样的。
  • 在语言中使用 { p with File, Metadata = f() } 之类的 f : unit -&gt; string * Metadata 也很好:)
  • 作为观察,这段代码混合了隐喻:它使用不可变对象(具有 File 成员的事物)来表示可变状态(磁盘上文件的名称)。虽然代码按照您的要求执行,但您很可能最终会在多线程/异步代码中得到两个图片对象:一个是移动之前的文件名(现在无效),另一个是移动之后的文件名。考虑使用具有可防止同时移动的可变成员的类,或者执行复制而不是移动,使对象 IDisposable 可删除,并让调用者负责使其成为事务性(糟糕)。
  • @JamesHugard 虽然这是真的,但在代码中使用不可变对象可能仍然有意义,因为真正防止“坏”事情发生要困难得多:创建两个引用同一个文件的实例,但只移动一个,移动磁盘上的文件(可能来自外部进程),...也就是说,我实际上试图找到一种方法来限制可变 File 字段的设置器,但没有成功。跨度>
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-13
相关资源
最近更新 更多