簡體   English   中英

繼承在Haskell中擴展數據結構

[英]Inheritance to extend a data structure in Haskell

一位C ++程序員試圖在這里學習Haskell。 請原諒這個可能很簡單的問題。 我想翻譯一個代表3D形狀的程序。 在C ++中我有類似的東西:

class Shape {
public:
  std::string name;
  Vector3d position;
};

class Sphere : public Shape {
public:
  float radius;
};

class Prism : public Shape {
public:
  float width, height, depth;
};

我試圖將其轉換為Haskell(使用記錄?),這樣我就可以擁有一些知道如何操作Shape的函數(比如訪問它的名稱和位置),以及其他只知道如何操作球體的函數,比如計算基於其位置和半徑的東西。

在C ++中,成員函數可以只訪問這些參數,但我很難弄清楚如何在Haskell中使用記錄,類型類或其他方法來執行此操作。

謝謝。

直截了當的翻譯。

type Vector3D = (Double, Double, Double)

class Shape shape where
    name :: shape -> String
    position :: shape -> Vector3D

data Sphere = Sphere {
    sphereName :: String,
    spherePosition :: Vector3D,
    sphereRadius :: Double
}

data Prism = Prism {
    prismName :: String,
    prismPosition :: Vector3D,
    prismDimensions :: Vector3D
}

instance Shape Sphere where
    name = sphereName
    position = spherePosition

instance Shape Prism where
    name = prismName
    position = prismPosition

但是你通常不會這樣做; 它是重復的,多態列表需要語言擴展。

相反,將它們粘貼到一個封閉的數據類型中可能是您​​應該采用的第一個解決方案。

type Vector3D = (Double, Double, Double)

data Shape
  = Sphere { name :: String, position :: Vector3D, radius :: Double }
  | Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }

您當然可以通過創建更多類型類來模擬多個級別的繼承:

class (Shape shape) => Prism shape where
    dimensions :: Vector3D
data RectangularPrism = ...
data TriangularPrism = ...
instance Prism RectangularPrism where ...
instance Prism TriangularPrism where ...

您也可以通過嵌入數據類型來模擬它。

type Vector3D = (Double, Double, Double)

data Shape = Shape { name :: String, position :: Vector3D }

data Sphere = Sphere { sphereToShape :: Shape, radius :: Double }
newSphere :: Vector3D -> Double -> Shape
newSphere = Sphere . Shape "Sphere"

data Prism = Prism { prismToShape :: Shape, dimensions :: Vector3D }

data RectangularPrism = RectangularPrism { rectangularPrismToPrism :: Prism }
newRectangularPrism :: Vector3D -> Vector3D -> RectangularPrism
newRectangularPrism = (.) RectangularPrism . Prism . Shape "RectangularPrism"

data TriangularPrism = TriangularPrism { triangularPrismToPrism :: Prism }
newTriangularPrism :: Vector3D -> Vector3D -> TriangularPrism
newTriangularPrism = (.) TriangularPrism . Prism . Shape "TriangularPrism"

但是在Haskell中模擬OO並不像Haskellish方式那樣令人滿意。 你想做什么?

(另請注意,所有這些解決方案只允許向上轉換,向下轉換是不安全的,也是不允許的。)

與阻止使用類型類的趨勢相反,我建議(正如你所學習的)探索沒有類型類的解決方案和一個用於解決各種方法的不同權衡的解決方案。

“單閉合數據類型”解決方案肯定比類型類更“功能”。 這意味着你的形狀模塊“固定”了你的形狀列表,而不是外部的新形狀可擴展。 添加在形狀上運行的新功能仍然很容易。

如果你有一個僅在單一形狀類型上運行的函數,那么你會有一點點不便,因為你放棄了靜態編譯器檢查傳入的形狀對於函數是否正確(參見Nathan的例子)。 如果你有很多這些部分函數只能在你的數據類型的一個構造函數上工作,我會重新考慮這個方法。

對於類型類解決方案,我個人寧願鏡像形狀類層次結構,而是為“具有表面區域的東西”,“具有體積的東西”,“具有半徑的東西”創建類型類,......

這允許你編寫只采用特定種類形狀的函數,比如球體(因為每個形狀都是它自己的類型),但你不能編寫一個帶有“任何形狀”的函數,然后區分各種具體的形狀。

就像Nathan所說,建模數據類型在Haskell和C ++中是完全不同的。 您可以考慮以下方法:

data Shape  = Shape  { name        :: String, position :: Vector3d }
data Sphere = Sphere { sphereShape :: Shape,  radius   :: Float }
data Prism  = Prism  { prismShape  :: Shape,  width    :: Float, height :: Float, depth :: Float }

換句話說,將對超類的引用建模為數據類型中的額外字段。 它很容易擴展到更長的繼承鏈。

不要使用類型類,如ephemient暗示。 這些用於重載函數,這根本不是問題:您的問題涉及數據建模,而不是行為

希望這可以幫助!

一個簡單的翻譯突破了不同的部分,但避免了類型類:

type Vector3D = (Float,Float,Float)
data Body = Prism Vector3D | Sphere Double
radius (Prism position) = -- code here
radius (Sphere r) = r

然后

data Shape = Shape {
  name :: String,
  position :: Vector3D,
  body :: Body
}

shapeOnly (Shape _ pos _) = -- code here

both = radius . body

sphereOnly (Shape _ _ (Sphere radius)) = -- code here
sphereOnly _ = error "Not a sphere"

不是一個非常簡單的問題。 C ++和Haskell之間的數據結構設計非常不同,所以我敢打賭,大多數來自OO語言的人都會問同樣的事情。 不幸的是,最好的學習方法是做; 你最好的選擇是根據具體情況進行嘗試,直到你了解Haskell的工作原理。

我的回答非常簡單,但它並不適用於單個C ++子類具有其他人沒有的方法的情況。 它會引發運行時錯誤,需要額外的代碼才能啟動。 您還必須決定“子類”模塊是否決定是拋出錯誤還是“超類”模塊。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM