繁体   English   中英

C#到F#:功能性思维与多态性

[英]C# to F#: Functional thinking vs. polymorphism

假设我有两个班:

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的基本版本)。 我应该如何在功能世界中构建这些类型来实现它? 我的问题在功能性世界中是否有意义? 对这种想法的任何评论都是最受欢迎的。

在我看来,你所描述的C#设计根本就是错误的 - 你用虚拟方法CanSuperManJumpOver定义了一个ShapeWithHeight类型,但是这个方法不能用于其中一个具体实例(2D三角形),你必须抛出异常。

在F#中对域进行建模时的一个关键原则是无效状态不应该是可表示的有关更多信息,请参阅此文章 )。 你的设计打破了这个 - 因为你可以构造一个三角形并在其上调用一个操作,但操作无效。

因此,首先要考虑的是,您尝试建模的域实际上是什么? (这有点难以从你的例子中猜出,但让我试试......)假设你有一些物体,超人可以跳过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设计)是使用一个存储公共属性( Shape )并包含值的类型另一种类型( Shape3DInfo ),指定每个子类的特定细节。

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

let CanSupermanJumpOver height = 
    height = TALL

当你有一个需要交替应用CanSupermanJumpOver函数的问题时,你才会开始考虑三角形和圆柱体之间的多态性。 基本上,OO范例专注于隐藏正在使用的内部数据结构。 功能范例侧重于封装复杂的流程逻辑,但数据结构是透明的。 诀窍在于尝试将这两种方法结合起来,而不会损害其中任何一种方法 这是我最后一直在苦苦挣扎的事情。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM