C# 到 F# : Functional thinking vs. 多态性

标签 c# architecture f# functional-programming

假设我有两个类:

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!)并且我想提取一个对任何具有 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#。

我一直在阅读一些关于函数式语言的资料。传统的想法是更喜欢表达代数值类型而不是继承类。人们的想法是,最好用较小的构建 block 组合或构建更丰富的类型,而不是从抽象类开始并从那里缩小范围。

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

最佳答案

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

在 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 设计)是使用存储公共(public)属性的类型(Shape) 并包含另一种类型 (Shape3DInfo) 的值,该类型指定每个子类的具体细节。

关于C# 到 F# : Functional thinking vs. 多态性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21813250/

相关文章:

architecture - 32 位与 64 位架构 - 虚拟地址空间

c# - 类型 'T' 与类型 'T' 不兼容

c# - 页面复制失败

c# - 将视频渲染到帧速率可能不一致的文件

c# - 移动跨平台游戏开发的最佳编程语言

database - Dropbox 的 ATF - 如何将函数/回调存储在数据库中?

html - 如何设计解耦的 HTML

c# - OnValidateIdentity ASP.NET Identity 如何工作

orm - F#-选择哪个ORM?

f# - 使用 F# 根据另一个列表减少列表的最快方法