使用接口

时间:2017-02-17 15:43:04

标签: f# immutability underlyingtype

我是F#的新手并试图设计一些类型,我注意到有多少OOP影响了我的设计决策。我很难找到这个特殊的问题而空手而归。

我将在C#中描述我想要做的事情,因为我对术语更熟悉。让我们说我有一个接口,在容器类上指定一些最小的必需方法。我们称之为IContainer。然后我有两个实现此接口的类,ContainerAContainerB具有对用户隐藏的不同底层实现。这是一种非常常见的OOP模式。

我试图在F#中实现同样的东西,只有不可变类型才能保留在功能世界中,即如何实现其功能可互换的类型,但用户将使用的公共函数保持不变:< / p>

type 'a MyType = ...

let func1 mytype = ...
let func2 mytype -> int = ...

MyType的定义未知,稍后可以更改,例如如果找到更高效的函数版本(比如更好地实现容器类型),但没有太多努力或需要重新设计整个模块。一种方法是在函数中使用模式匹配和区分联合,但这似乎不太可扩展。

1 个答案:

答案 0 :(得分:2)

在函数式语言中,使用比OO语言更简单的类型更为典型。

建模形状是典型的例子。

这是典型的面向对象方法:

type IShape =
     abstract member Area : double

type Circle(r : float) =
     member this.Area = System.Math.PI * r ** 2.0
     interface IShape with
         member this.Area = this.Area

type Rectangle(w : float, h : float) =
     member this.Area = w * h
     interface IShape with
         member this.Area = this.Area

请注意,使用此方法添加新类型非常容易,我们可以相对轻松地引入TriangleHexagon类。我们只需创建类型并实现接口。

相比之下,如果我们想在我们的Perimeter中添加新的IShape成员,我们就必须更改每项可能需要大量工作的实施。

现在让我们看看我们如何用函数式语言建模:

type Shape =
    |Circle of float
    |Rectangle of float * float

[<CompilationRepresentation (CompilationRepresentationFlags.ModuleSuffix)>]
module Shape =
    let area = function
        |Circle r -> System.Math.PI * r ** 2.0
        |Rectangle (w, h) -> w*h

现在,希望您可以看到添加perimeter函数要容易得多,我们只是针对每个Shape案例进行模式匹配,编译器可以检查我们是否已经为每个案例详尽地实施了它。

相比之下,现在添加新的Shape要困难得多,因为我们必须返回并更改对Shape s起作用的所有函数。

结果是,无论我们选择使用何种形式的建模,都需要权衡利弊。此问题称为表达式问题

您可以轻松地将第二种模式应用于Container问题:

type Container =
    |ContainerA
    |ContainerB

let containerFunction1 = function
    |ContainerA -> ....
    |ContainerB -> ....

这里有一个包含两个或更多个案例的单一类型,每个案例的唯一功能实现都包含在模块函数中,而不是类型本身。