【问题标题】:Designing F# types with hidden underlying implementations from interface从接口设计具有隐藏底层实现的 F# 类型
【发布时间】:2017-07-07 05:12:18
【问题描述】:

我是 F# 的新手,在尝试设计某些类型时,我注意到 OOP 对我的设计决策有多大影响。我很难找到这个特殊的问题,结果空手而归。

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

我试图在 F# 中仅使用不可变类型来实现相同的功能以留在功能世界中,即如何实现其功能可互换但用户将使用的公共功能保持不变的类型:

type 'a MyType = ...

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

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

【问题讨论】:

  • 那么您是否尝试在 F# 中做完全相同的事情,因为您可以在其中实现类和接口。
  • 是的。一种可能性是将设计完全从 C# 复制到 F#,但底层实现必须是派生类的一部分。例如,在容器上运行的函数是否应该调用container.IsEmpty?以及如何设计类/类型以使let add ... = 不会修改对象的状态?
  • 目前尚不清楚您是尝试在 F# 中执行 OOP,还是从功能角度重新考虑问题。您想以 F# 惯用的方式完成接口的功能,还是想在代码中使用文字接口?
  • 如果您所做的只是在 F# 中重新进行 OOP 设计,那么我认为首先使用 F# 没有什么意义。是的,这是可能的,但除此之外呢?您的用例很可能适用于功能解决方案,但您提供的上下文很少 - 事实上,提供的上下文是您的 OOP 设计。能多说一点吗?
  • 一个非常广泛的问题。我强烈推荐 1) fsharp.org 上的文档下方的“F# 组件设计指南”,以及 2) 标题为 F# 的网站,以获得您的答案。

标签: f# immutability underlyingtype


【解决方案1】:

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

造型造型是典型的例子。

这是一个典型的 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 类,而且工作量相对较小。我们只需创建类型并实现接口。

相比之下,如果我们想在IShape 中添加一个新的Perimeter 成员,我们将不得不更改每个实现,这可能需要大量工作。

现在让我们看看如何用函数式语言对形状进行建模:

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 案例进行模式匹配,编译器就可以检查我们是否已经针对每个案例详尽地实现了它。

相比之下,现在添加新的Shapes 要困难得多,因为我们必须返回并更改作用于Shapes 的每个函数。

结果是,无论我们选择使用何种形式的建模,都需要权衡取舍。这个问题被称为表达式问题


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

type Container =
    |ContainerA
    |ContainerB

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

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

【讨论】:

  • 我喜欢在 OO 和 FP 范式之间进行权衡的观点,我不知道表达问题。谢谢!并感谢所有其他有帮助的评论者!
  • @NordCoder 值得注意的是,该问题还有其他解决方案。如果您有一个很好的临时多态性系统(即类型类/特征),那将开辟另一种选择。 .NET 目前不支持它们,尽管最近演示了一种实现它们的机制。
  • 我知道这一点,实际上我的 F# 代码受到 Haskell 库的启发,该库利用类型类来实现这一点。据我了解,与 Haskell 相比,F#“typeclass”实现有点受限,但如果我错了,请纠正我。
猜你喜欢
  • 2016-11-16
  • 1970-01-01
  • 2016-10-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-05
  • 1970-01-01
相关资源
最近更新 更多