MiP*_*MiP 2 .net c# f# functional-programming record
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
Run Code Online (Sandbox Code Playgroud)
我有两个问题:
init
上面的函数而无需:> IController
每条记录?回答第一个问题:不,您不能每次都摆脱up弃。F#不执行自动类型强制(这是一件好事),并且所有match
分支必须具有相同的类型。因此,唯一要做的就是手动强制。
对第二个问题的答案:有区别的工会代表“封闭世界的假设”-也就是说,当您事先知道不同案件的数量并且您对以后再扩展它们不感兴趣时(您的世界是“封闭的”),工会是很好的。在这种情况下,您可以让编译器帮助您确保所有处理您的事情的人都能处理所有情况。对于某些应用程序,此功能超级强大。
另一方面,有时您需要以某种方式设计您的东西,以便以后可以通过外部插件进行扩展。这种情况通常被称为“开放世界假设”。在这种情况下,接口起作用。但是它们不是唯一的方法。
接口只不过是函数的记录,方法通用性除外。如果您对通用方法不感兴趣,并且以后不打算向下转换到特定的实现(无论如何这样做都是一件坏事),您可以将“开放世界”表示为功能记录:
type Controller = {
``type``: ControllerType
controlSomething: ControllableThing -> ControlResult
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以通过提供不同的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 */ }
Run Code Online (Sandbox Code Playgroud)
Incidentally, this also gets rid of upcasting, since now everything is of the same type. Also incidentally, your code is much smaller now, since you don't have to explicitly define all different controllers as their own types.
Q: Wait, but now, how do I get access to ram
and rom
and romMode
from outside?
A: Well, how were you going to do it with the interface? Were you going to downcast the interface to a specific implementation type, and then access its fields? If you were going to do that, then you're back to the "closed world", because now everybody who handles your IController
needs to know about all implementation types and how to work with them. If this is the case, you would be better off with a discriminated union to begin with. (like I said above, downcasting is not a good idea)
On the other hand, if you're not interested in downcasting to specific types, it means that you're only interested in consuming the functionality that all controllers implement (this is the whole idea of interfaces). If this is the case, then a record of functions is sufficient.
Finally, if you are interested in generic methods, you have to use interfaces, but you still don't have to declare everything as types, for F# has inline interface implementations:
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
Run Code Online (Sandbox Code Playgroud)
This is a little more verbose than records, and you can't easily amend them (i.e. no { ... with ... }
syntax for interfaces), but if you absolutely need generic methods, it's possible.