我是F#的新手并试图设计一些类型,我注意到有多少OOP影响了我的设计决策。我很难找到这个特殊的问题而空手而归。
我将在C#中描述我想要做的事情,因为我对术语更熟悉。让我们说我有一个接口,在容器类上指定一些最小的必需方法。我们称之为IContainer
。然后我有两个实现此接口的类,ContainerA
和ContainerB
具有对用户隐藏的不同底层实现。这是一种非常常见的OOP模式。
我试图在F#中实现同样的东西,只有不可变类型才能保留在功能世界中,即如何实现其功能可互换的类型,但用户将使用的公共函数保持不变:< / p>
type 'a MyType = ...
let func1 mytype = ...
let func2 mytype -> int = ...
MyType的定义未知,稍后可以更改,例如如果找到更高效的函数版本(比如更好地实现容器类型),但没有太多努力或需要重新设计整个模块。一种方法是在函数中使用模式匹配和区分联合,但这似乎不太可扩展。
答案 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
请注意,使用此方法添加新类型非常容易,我们可以相对轻松地引入Triangle
或Hexagon
类。我们只需创建类型并实现接口。
相比之下,如果我们想在我们的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 -> ....
这里有一个包含两个或更多个案例的单一类型,每个案例的唯一功能实现都包含在模块函数中,而不是类型本身。