簡體   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