2017-04-14 197 views
2

F#记录不能被继承,但它们可以实现接口。例如,我想创建不同的控制器:不使用接口访问F#记录基本属性

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 

我有2个问题:

  1. 当我创建一个控制器记录,我需要把它上溯造型到一个接口。有没有更好的方法来编写上面的init函数而没有:> IController每条记录​​?
  2. 我试过歧视的工会,但不知何故最终编写interfance像这个例子。但接口是一个.NET的东西,我怎样才能以功能的方式重写这个例子,用组合而不是继承?

回答

4

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

回答了第二个问题:歧视工会代表“封闭世界假设” - 也就是说,他们是很好的,当你知道不同的病例数的前期,而你不感兴趣,后来扩展它们(您世界是“封闭的”)。在这种情况下,你可以让编译器帮助你确保每个使用你的东西的人都能处理所有的情况。这对于某些应用程序来说非常强大。

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

接口只不过是功能记录,除了method genericity。如果你对通用方法不感兴趣,你不打算在以后向具体实现向下转换(这将是一件坏事),你可以将你的“开放世界”作为函数记录来表示:

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

现在,你可以通过提供不同controlSomething实现创建不同类型的控制器:

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 */ } 

顺便说一句,这也摆脱上溯造型的,因为现在一切都相同类型的。此外,现在您的代码更小,因为您不必将所有不同的控制器明确定义为它们自己的类型。

问:等待,但现在,我怎么可以访问外部ramromromMode

- 答:那么,你打算如何使用界面呢?你是否将界面翻译成特定的实现类型,然后访问其字段?如果你打算这样做,那么你又回到了“封闭世界”,因为现在每个处理你的人都需要知道所有实现类型以及如何使用它们。如果是这样的话,你最好先与一个有歧视的工会谈谈。(就像我上面所说的,向下转换并不是一个好主意)

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

最后,如果你感兴趣的通用方法,你必须使用接口,但仍然没有宣布一切,类型,F#有内嵌的接口实现:

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 

这比记录更详细一些,你不能轻易修改它们(即没有接口的{ ... with ... }语法),但是如果你绝对需要泛型方法,这是可能的。

+1

使用函数是greate。我不需要访问'ram','rom'和'romMode',但是我可以在哪里存储这些数据以使用后者? – MiP

+2

您可以将它们存储在您的函数可以访问的闭包中。看看我的例子:看看我在'controlSomething'的实现中如何使用'ram'和'rom'?你甚至可以让它们变化! (虽然我强烈建议不要) –

+1

我忘了我正在使用F#,谢谢。 – MiP

2

答到的第一个问题:你可以让编译器做的大部分工作:

let init = function 
| Basic -> 
    { rom = [||]; ``type`` = Basic } :> IController 
| Advanced1 | Advanced1RAM | Advanced1RAMBattery as t -> 
    { ram = [||]; rom = [||]; ``type`` = t } :> _ 
| Advanced2 -> 
    upcast { romMode = 0xFFuy; rom = [||]; ``type`` = Advanced2 } 

也就是说,一旦指定返回类型,然后让编译器填充为_或使用upcast