一位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中使用记录,类型类或其他任何方法来执行此操作。
感谢。
答案 0 :(得分:22)
直接的翻译。
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方式那样令人满意。你想做什么?
(另请注意,所有这些解决方案仅允许向上转发,向下转换是不安全的并且不允许。)
答案 1 :(得分:13)
与阻止使用类型类的趋势相反,我建议(正如您所学习的)探索没有类型类的解决方案,以及一种方法,以了解各种方法的不同权衡。< / p>
“单一闭合数据类型”解决方案当然比类型更具“功能性”。这意味着你的形状模块“固定”了你的形状列表,而不是外部的新形状可扩展。在形状上添加新功能仍然很容易。
如果你有一个仅在单一形状类型上运行的函数,那么你会有一些不便,因为你放弃了静态编译器检查传入的形状对于函数是否正确(参见Nathan的例子)。如果你有很多这些部分函数仅适用于你的数据类型的一个构造函数,我会重新考虑这种方法。
对于类型类解决方案,我个人宁愿不镜像形状类层次结构,而是为“具有表面区域的东西”,“具有卷的东西”,“具有卷的东西”创建类型类半径“,......
这允许你编写采用特定形状的函数,比如球体,只有(因为每个形状都是它自己的类型),但是你不能编写一个带有“任何形状”的函数然后区分各种具体的各种形状。
答案 2 :(得分: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 }
换句话说,将对超类的引用建模为数据类型中的额外字段。它很容易扩展到更长的继承链。
不要使用类型类,如ephemient暗示。这些用于重载功能,这根本不是问题:您的问题涉及数据的建模,而不是行为。
希望这有帮助!
答案 3 :(得分:2)
一个简单的翻译突破了变化的部分,但避免了类型类:
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 ++子类具有其他人没有的方法的情况。它会引发运行时错误,需要额外的代码才能启动。您还必须决定“子类”模块是否决定是抛出错误还是“超类”模块。