c# - 在不使用接口(interface)的情况下访问 F# 记录基本属性

标签 c# .net f# functional-programming record

F#记录不能被继承,但是可以实现接口(interface)。例如,我想创建不同的 Controller :

type ControllerType =
    | Basic
    | Advanced1
    | Advanced1RAM
    | Advanced1RAMBattery
    | Advanced2

// base abstract class
type IController =
    abstract member rom : byte[]
    abstract member ``type`` : ControllerType

type BasicController =
    { rom : byte[]
      ``type`` : ControllerType }
    interface IController with
        member this.rom = this.rom
        member this.``type`` = this.``type``

type AdvancedController1 =
    { ram : byte[]
      rom : byte[]
      ``type`` : ControllerType }
    interface IController with
        member this.rom = this.rom
        member this.``type`` = this.``type``

type AdvancedController2 =
    { romMode : byte
      rom : byte[]
      ``type`` : ControllerType }
    interface IController with
        member this.rom = this.rom
        member this.``type`` = this.``type``

let init ``type`` =
    match ``type`` with
    | Basic ->
        { rom = Array.zeroCreate 0
          ``type`` = Basic } :> IController
    | Advanced1 | Advanced1RAM | Advanced1RAMBattery ->
        { ram = Array.zeroCreate 0
          rom = Array.zeroCreate 0
          ``type`` = ``type`` } :> IController
    | Advanced2 ->
        { romMode = 0xFFuy
          rom = Array.zeroCreate 0
          ``type`` = ``type`` } :> IController

我有两个问题:

  1. 当我创建一个 Controller 记录时,我需要将它上传到一个接口(interface)。有没有更好的方法来编写上面的 init 函数而不用 :> IController 每条记录?
  2. 我尝试过可区分的联合,但不知何故最终写出了像这个例子这样的接口(interface)。但是接口(interface)是 .NET 的东西,我怎样才能以功能方式重写示例,使用组合而不是继承?

最佳答案

第一个问题的答案:不,你不能每次都摆脱向上转型。 F# 不执行自动类型强制转换(这是一件好事),并且所有 match 分支必须具有相同的类型。所以唯一能做的就是手动强制。

第二个问题的答案:受歧视的联合代表“封闭世界假设”——也就是说,当您预先知道不同情况的数量并且您对扩展不感兴趣时​​,它们是好的他们稍后(你的世界是“封闭的”)。在这种情况下,您可以让编译器帮助您确保与您的事物打交道的每个人都能处理所有情况。这对于某些应用程序来说非常强大。

另一方面,有时你需要设计你的东西,以便以后可以扩展,可能是通过外部插件。这种情况通常被称为“开放世界假设”。在这种情况下,接口(interface)起作用。但它们不是唯一的方法。

接口(interface)只不过是函数的记录,method genericity除外。 .如果您对通用方法不感兴趣并且您不打算稍后向下转换为特定的实现(无论如何这都是一件坏事),您可以只代表您的“开放世界”事物作为功​​能记录:

type Controller = { 
   ``type``: ControllerType
   controlSomething: ControllableThing -> ControlResult
}

现在您可以通过提供不同的 controlSomething 实现来创建不同类型的 Controller :

let init ``type`` =
    match ``type`` with
    | Basic ->
        let rom = Array.zeroCreate 0
        { ``type`` = Basic
          controlSomething = fun c -> makeControlResult c rom }

    | Advanced1 | Advanced1RAM | Advanced1RAMBattery ->
        let ram = Array.zeroCreate 0
        let rom = Array.zeroCreate 0
        { ``type`` = ``type`` 
          controlSomething = fun c -> makeControlResultWithRam c rom ram }

    | Advanced2 ->
        let romMode = 0xFFuy
        let rom = Array.zeroCreate 0
        { ``type`` = ``type`` 
          controlSomething = fun c -> /* whatever */ }

顺便说一句,这也摆脱了向上转型,因为现在所有东西都是同一类型。另外顺便说一句,您的代码现在要小得多,因为您不必将所有不同的 Controller 显式定义为它们自己的类型。

问:等等,但是现在,我如何从外面?

答:那么,您打算如何处理界面?您打算将接口(interface)向下转换为特定的实现类型,然后访问它的字段吗?如果您打算这样做,那么您将回到“封闭世界”,因为现在处理您的 IController 的每个人都需要了解所有实现类型以及如何使用它们。如果是这种情况,你最好从一个受歧视的工会开始。 (就像我上面说的,向下转换不是一个好主意)

另一方面,如果您对向下转换为特定类型不感兴趣,则意味着您只对使用所有 Controller 实现的功能感兴趣(这就是接口(interface)的全部概念)。如果是这种情况,那么功能记录就足够了。

最后,如果您对泛型方法感兴趣,您必须使用接口(interface),但您仍然不必将所有内容都声明为类型,因为 F# 具有内联接口(interface)实现:

type Controller =  
   abstract member ``type``: ControllerType
   abstract member genericMethod: 'a -> unit

let init ``type`` =
    match ``type`` with
    | Basic ->
        let rom = Array.zeroCreate 0
        { new Controller with 
             member this.``type`` = Basic
             member this.genericMethod x = /* whatever */ }

    // similar for other cases

这比记录要冗长一点,而且你不能轻易地修改它们(即接口(interface)没有 { ... with ... } 语法),但如果你绝对需要通用方法,这是可能的。

关于c# - 在不使用接口(interface)的情况下访问 F# 记录基本属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43413980/

相关文章:

c# - 使用C#计算PDF文档中减号的数量

c# - 将 2 个列表合并到一个新列表中,该列表包含列表 1 和列表 2 中的数据 - 其中唯一键为 "VariantId"

visual-studio - 如何制作 F# 的类图?

f# - 数据包超时异常

c# - 将 DataTable 中的列值从 byte[] 转换为 String

c# - 我如何在 c# 中创建 [experimental] 标签,它会引发编译器警告

.net - 何时在 ForEach 循环中获取属性?

c# - 强制 DataGrid 列验证 (WPF)

.net - 汇编和DLL之间的区别

f# - 如何在 F# 中正确访问自己定义的类型中的元素