繁体   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