继承在Haskell中扩展数据结构

Ste*_*eve 23 oop inheritance 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;
};
Run Code Online (Sandbox Code Playgroud)

我试图将其转换为Haskell(使用记录?),这样我就可以拥有一些知道如何操作Shape的函数(比如访问它的名称和位置),以及其他只知道如何操作球体的函数,比如计算基于其位置和半径的东西.

在C++中,成员函数可以只访问这些参数,但我很难弄清楚如何在Haskell中使用记录,类型类或其他方法来执行此操作.

谢谢.

eph*_*ent 23

直截了当的翻译.

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
Run Code Online (Sandbox Code Playgroud)

但是你通常不会这样做; 它是重复的,多态列表需要语言扩展.

相反,将它们粘贴到一个封闭的数据类型中可能是您​​应该采用的第一个解决方案.

type Vector3D = (Double, Double, Double)

data Shape
  = Sphere { name :: String, position :: Vector3D, radius :: Double }
  | Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }
Run Code Online (Sandbox Code Playgroud)

您当然可以通过创建更多类型类来模拟多个级别的继承:

class (Shape shape) => Prism shape where
    dimensions :: Vector3D
data RectangularPrism = ...
data TriangularPrism = ...
instance Prism RectangularPrism where ...
instance Prism TriangularPrism where ...
Run Code Online (Sandbox Code Playgroud)

您也可以通过嵌入数据类型来模拟它.

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"
Run Code Online (Sandbox Code Playgroud)

但是在Haskell中模拟OO并不像Haskellish方式那样令人满意.你想做什么?

(另请注意,所有这些解决方案只允许向上转换,而向下转换是不安全的,也是不允许的.)

  • 你将如何代表一个超过一个级别的继承链?例如`shape` - >`Prism` - >`RectangularPrism` (2认同)

Rüd*_*nke 13

与阻止使用类型类的趋势相反,我建议(正如你所学习的)探索没有类型类的解决方案和一个用于解决各种方法的不同权衡的解决方案.

"单闭合数据类型"解决方案肯定比类型类更"功能".这意味着你的形状模块"固定"了你的形状列表,而不是外部的新形状可扩展.添加在形状上运行的新功能仍然很容易.

如果你有一个仅在单一形状类型上运行的函数,那么你会有一点点不便,因为你放弃了静态编译器检查传入的形状对于函数是否正确(参见Nathan的例子).如果你有很多这些部分函数只能在你的数据类型的一个构造函数上工作,我会重新考虑这个方法.

对于类型类解决方案,我个人宁愿镜像形状类层次结构,而是为"具有表面区域的东西","具有体积的东西","具有半径的东西"创建类型类,......

这允许你编写只采用特定种类形状的函数,比如球体(因为每个形状都是它自己的类型),但你不能编写一个带有"任何形状"的函数,然后区分各种具体的形状.


Mar*_*ijn 7

就像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 }
Run Code Online (Sandbox Code Playgroud)

换句话说,将对超类的引用建模为数据类型中的额外字段.它很容易扩展到更长的继承链.

不要使用类型类,如ephemient暗示.这些用于重载函数,这根本不是问题:您的问题涉及数据建模,而不是行为.

希望这可以帮助!