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
我有两个问题:
init
函数而不使用:> IController
每个记录?答案 0 :(得分:5)
回答第一个问题:不,你不能每次都摆脱向上转播。 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 */ }
顺便说一句,这也消除了向上转换,因为现在一切都属于同一类型。顺便提一下,您的代码现在要小得多,因为您不必将所有不同的控制器明确定义为自己的类型。
问:等等,但现在,如何从外部访问ram
和rom
以及romMode
?
A:嗯,你打算怎么用界面做的?您是否要将接口转发为特定的实现类型,然后访问其字段?如果您打算这样做,那么您将回到“封闭世界”,因为现在处理您的IController
的每个人都需要了解所有实现类型以及如何使用它们。如果是这种情况,那么你最好先选择一个有区别的联盟。 (就像我上面说的那样,向下倾斜并不是一个好主意)
另一方面,如果您对向下转换为特定类型不感兴趣,则意味着您只对使用所有控制器实现的功能感兴趣(这是接口的全部概念)。如果是这种情况,那么功能记录就足够了。
最后,如果 对通用方法感兴趣,则必须使用接口,但您仍然不必将所有内容声明为类型,因为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 :(得分:2)
回答第一个问题:您可以让编译器完成大部分工作:
2017-04-14T21:22:49.344+02:00[Europe/Berlin]
即,指定一次返回类型,然后让编译器将其填入let init = function
| Basic ->
{ rom = [||]; ``type`` = Basic } :> IController
| Advanced1 | Advanced1RAM | Advanced1RAMBattery as t ->
{ ram = [||]; rom = [||]; ``type`` = t } :> _
| Advanced2 ->
upcast { romMode = 0xFFuy; rom = [||]; ``type`` = Advanced2 }
或使用_