【问题标题】:C# to F#: Functional thinking vs. polymorphismC# 到 F#:函数式思维与多态性
【发布时间】:2014-03-15 19:33:03
【问题描述】:

假设我有两个班级:

public class Triangle {
    public float Base { get; set; }
    public float Height { get; set; }

    public float CalcArea() { return Base * Height / 2.0; }
}

public class Cylinder {
    public float Radius { get; set; }
    public float Height { get; set; }

    public float CalcVolume() { return Radius * Radius * Math.PI * Height }
}

我们这里有两个几何形状的描述以及两者的操作。

这是我在 F# 中的尝试:

type Triangle = { Base: float; Height: float }
module TriangleStuff = 
    let CalcArea t =
        t.Base * t.Height / 2.0

type Cylinder = { Radius: float; Height: float }
module CylinderStuff = 
    let CalcVolume c = 
        c.Radius * c.Radius * Math.PI * c.Height

假设我对这两个类进行了观察(它们都有Height!)并且我想提取一个对任何具有高度属性的东西都有意义的操作。所以在 C# 中我可能会拉出一个基类并在那里定义操作,如下所示:

public abstract class ShapeWithHeight {
    public float Height { get; set; }
    public virtual bool CanSuperManJumpOver() { 
        return Height == TALL;  // Superman can *only* jump over tall buildings
    }
    public const float TALL = float.MaxValue;
}

public class Triangle : ShapeWithHeight {
    public float Base { get; set; }

    public float CalcArea() { return Base * Height / 2.0; }

    public override bool CanSuperManJumpOver() {
        throw new InvalidOperationException("Superman can only jump over 3-d objects");
    }
}

public class Cylinder : ShapeWithHeight {
    public float Radius { get; set; }
    public float CalcVolume() { return Radius * Radius * Math.PI * Height }
}

请注意各个子类对于此操作的实现可能有自己的想法。

言归正传,我可能在某个地方有一个函数可以接受 一个三角形或一个圆柱体:

public class Superman {
    public void JumpOver(ShapeWithHeight shape) {
        try {
            if (shape.CanSuperManJumpOver()) { Jump (shape); }
        } catch {
            // ...
        }
    }
}

.. 这个函数可以接受三角形或圆柱体。

我无法将相同的思路应用到 F#。

我一直在阅读有关函数式语言的文章。传统的想法是更喜欢表达代数值类型而不是继承类。这种想法认为,最好用更小的构建块来组合或构建更丰富的类型,而不是从抽象类开始并从那里缩小。

在 F# 中,我希望能够定义一个函数,该函数接受一个已知具有 Height 属性的参数,并以某种方式使用它(即 CanSuperManJumpOver 的基本版本)。我应该如何在功能世界中构建这些类型来实现它?我的问题在功能世界中是否有意义?欢迎任何有想法的cmets。

【问题讨论】:

    标签: c# architecture f# functional-programming


    【解决方案1】:

    在我看来,您所描述的 C# 设计从根本上是错误的 - 您使用虚拟方法 CanSuperManJumpOver 定义了一个类型 ShapeWithHeight,但该方法无法为其中一个具体实例(二维三角形)实现,并且您必须抛出异常。

    在 F# 中对域进行建模时的一个关键原则是无效状态不应该是可表示的(参见 great article for more)。您的设计打破了这一点 - 因为您可以构造一个三角形并对其调用操作,但该操作是无效的。

    因此,首先要考虑的是,您要建模的领域实际上是什么? (从你的例子中这有点难以猜测,但让我试试......)假设你有一些物体,超人可以跳过 3D 形状,但不能跳过 2D 形状。您可以使用有区别的联合来区分这两种形状:

    type Height = float
    type Shape2DInfo = 
      | Triangle of float * float
    type Shape3DInfo = 
      | Cylinder of float
    
    type Shape = 
      | Shape2D of Shape2DInfo
      | Shape3D of Height * Shape3DInfo
    

    诀窍在于,对于所有 3D 形状,我们现在可以在 Shape3D 案例中直接获得高度 - 因此您始终可以获得 3D 形状的高度(无论它是哪种特定形状 - 这里只有圆柱体)。对于 2D 形状,我没有包括高度,因为它们可能有,也可能没有...

    然后你可以编写一个模式匹配形状的跳跃函数并处理三种不同的情况 - 形状不可跳跃,形状太小或形状足够高:

    let jumpOver shape = 
      match shape with
      | Shape2D _ -> printfn "Cannot jump!"
      | Shape3D(height, _) ->
          if height = Double.MaxValue then printf "Jumped!"
          else printfn "Too boring!"
    

    总而言之——如果你有一个在 C# 中有一些属性的抽象类,那么在 F# 中最接近的事情(如果你想使用功能设计,而不是 OO 设计)是使用存储公共属性的类型(@987654328 @) 并包含另一种类型的值 (Shape3DInfo),它指定了每个子类的特定详细信息。

    【讨论】:

    • 好的,关于这个例子的选择很糟糕,但这忽略了问题的重点:我可以创建对已知具有特定属性的事物起作用的函数吗?在 C# 中,我可以使用抽象基类型。
    • 我添加了更多详细信息,并举例说明了如何在 F# 中执行类似操作。
    • 这很有趣。您关于避免无效状态的观点很好。因此,如果我想发明一种新类型Box,它具有惯用的 Length、Width、Height 属性,这将如何适应?会不会是 Shape3DInfo 类型中的另一种选择?
    • @CSJ - 是的,这就是我在这里的想法(你会添加一个长度和宽度的案例,高度会自动添加到所有 3D 形状)。也就是说,我认为这个例子(以及我对它的错误理解:-))可能无法很好地解释这一点。我真的建议查看我链接的文章以获得更好的解释 (fsharpforfunandprofit.com/posts/…)
    • @CSJ - 是的,这是一般模式。也就是说,如果您需要 OO 风格的可扩展性(能够轻松添加新的子类),那么您仍然可以在 F# 中使用接口。 (虽然大多数时候,从有区别的工会开始会更容易。)
    【解决方案2】:

    在函数式编程范例中,您将从函数开始,然后从中计算出类型。所以起点就是函数

    let CanSupermanJumpOver height = 
        height = TALL
    

    只有当您遇到需要在两者上交替应用 CanSupermanJumpOver 函数的问题时,您才会开始考虑三角形和圆柱体之间的多态性。基本上,OO 范式侧重于隐藏正在使用的内部数据结构。功能范式侧重于封装复杂的流程逻辑,但数据结构是透明的。诀窍是试图在不损害任何一种方法的好处的情况下结合这两种方法。这是我过去一段时间一直在努力解决的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-16
      • 2018-09-20
      • 1970-01-01
      • 1970-01-01
      • 2010-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多