[英]Why does Haskell allow a list of Shape, but no list of Square or Circle or Triangle
为什么Haskell允许在第一个例子中做一个Shape列表,但不像第二个例子那样? 据我所知,两个列表都有元素
{ name :: String, position :: Vector3D, radius :: Double }
要么
{ name :: String, position :: Vector3D, dimensions :: Vector3D }
。
前1:
data Shape
= Sphere { name :: String, position :: Vector3D, radius :: Double }
| Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }
前2:
data Sphere = Sphere { name :: String, position :: Vector3D, radius :: Double }
data Prism = Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }
我想知道为什么可以做一个Shape列表,但没有Sphere和Prism的多态列表,即使它们具有与通过数据类型“Shape”声明的成员相同的成员。
在你的第二个例子中,
data Sphere = SphereTag { sphereName :: String,
spherePosition :: Vector3D,
sphereRadius :: Double }
data Prism = PrismTag { prismName :: String,
prismPosition :: Vector3D,
prismDimensions :: Vector3D }
你已经声明这些是两种不同的数据类型。 您可以使用[Sphere]
和[Prism]
但不能使用[Shape]
(因为在此示例中未定义Shape
类型)。
我已经重命名了这些字段,因为否则name
有两个类型name :: Sphere -> String
和name :: Prism -> String
,如果不使用类型类,则不允许这样做。
我重命名了SphereTag
和PrismTag
,以明确Sphere
类型和数据构造函数SphereTag
之间的区别
在第一个例子中
data Shape
= SphereShape { name :: String, position :: Vector3D, radius :: Double }
| PrismShape { name :: String, position :: Vector3D, dimensions :: Vector3D }
有一种类型,所以你可以制作[Shape]
。
结合两种类型的经典方法是使用Either,它使用Left
或Right
标记来自两种类型的数据:
type PrismOrSphere = Either Prism Sphere
myList = [Left (SphereTag "this" ...), Right (PrismTag "that" ....), ....]
但你的自定义Shape类型可能会更好。
尽量不要将OOP教学示例重新用作函数式编程示例。 OOP示例旨在首先教授OO原则,然后编程编程,它们的设计非常适合开发OO和命令式思维。
这就像试图通过在空车停车场学习驾驶飞机一样。 在飞机上停车很慢,但这可能是你开车时学到的第一件事。
如果你坚持通过试图复制你的驾驶课程来学习飞行,你会发现你的飞机是一种非常不方便的汽车,不适合很多道路。
您应该使用一套精心编写的示例来教授函数式编程。 我推荐Learn You a Haskell for Great Good ,它包含web和dead tree版本。
在面向对象编程中,您通常称为多态的是使用超类实现的。 您可以拥有由Shape的子类型组成的ShapeList或由管理器和清理器组成的EmployeeList,但是在传统的OOP中,您需要编写不同但相似的代码来实现每个方法的.sort
方法。 我们可以称之为这种亚型多态性。 它与从泛型中获得的多态性不同,在泛型中您可以编写单个方法来处理任何类型。
在函数式编程中,你通常称之为多态的是通过对数据类型完全不可知来实现的,因此你可以编写一个函数reverse :: [a] -> [a]
,它将适用于任何可能的Shapes列表或者Spheres或Employees等等,更像是泛型但没有运行时类型的数据开销(请参阅Haskell擦除类型吗? )。 我们可以称之为参数多态。 它与从类型类中获得的多态性不同,在类型类中,允许多个类型具有相同的命名函数。
因为在ex1中, Sphere
和Prism
不是类型而是构造函数。 它们都是相同类型的Shape
。 因此,您可以创建[Shape]
列表。 而且,由于它们不是类型, [Sphere]
甚至没有意义,因为Sphere
不在类型命名空间中。 我不知道这是否令你感到困惑,但是当我开始学习Haskell时,我混淆了构造函数和OO子类。 他们是不同的。 在那种情况下( ex1 )你不能有这样的事情:
radius :: Sphere -> Double
因为Sphere
不是一种类型。 Sphere
是一个返回Shape
的函数。
现在,在ex2中 , Sphere
和Prism
是你所拥有的类型
[Sphere]
[Prim]
[Either Sphere Prism]
指出的一个或一个顺序的列表。 另一种解决方案是为具有名称和位置的东西声明Shape
类。
class Shape a where
name :: a -> String
position :: a -> Vector3D
data Sphere = Sphere { sphereName :: String, spherePosition :: Vector3D, ... }
instance Shape Sphere where
name s = sphereName s
position s = spherePosition
data Prism = Prism { primsName :: String, prismPosition :: Vector3D, ... ?
instance Shape Prirm where
name p = prismName p
position p = prismPosition p
现在,您可以拥有一个列表同质的形状,例如:
names :: Shape s => [s] -> [String]
names ss = map name ss
同质,我的意思是你不能在同一个列表中混合棱镜和球体。
你所说的实际上并不正确,你提供的例子都不是列表。
data Shape
= Sphere { name :: String, position :: Vector3D, radius :: Double }
| Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }
以上示例是Algebric数据类型的示例。
您显示的第二个示例是Sphere的记录数据声明示例:
data Sphere = Sphere { name :: String, position :: Vector3D, radius :: Double }
data Prism = Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }
话虽如此,在Haskell列表中是同类型的。 它们只能保存相同类型的值。 (虽然你可以通过使用存在量化来克服这一点,但不要像反模式那样做。)
同样从你的例子中,你可以将它作为[Shape]
类型的列表,它可以包含Sphere
和Prism
。 例:
λ> let a = Sphere "sph1" 2.2 3.4
λ> let b = Prism "prism" 3.4 4.8
λ> let c = [a,b]
λ> :t c
c :: [Shape]
在上面的例子中,我假设Vector3D
是Double
类型。
如果我理解你,听起来你理解的是数据类型
data Shape
= Sphere { name :: String, position :: Vector3D, radius :: Double }
| Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }
和数据类型
data Sphere = Sphere { name :: String, position :: Vector3D, radius :: Double }
data Prism = Prism { name :: String, position :: Vector3D, dimensions :: Vector3D }
是一回事。 他们不是(但从OOP的角度来看,这是一个可以理解的假设)!
Shape
是所谓的“Sum Type” - 一种数据类型,可以有多种形式之一,在本例中为Sphere
或Prism
。 你可以创建一个Shape
的“多态列表”,因为从本质上讲, 数据类型本身就是多态的 。 Shape
可以有两种不同类型的值。
在第二个示例中, Sphere
和Prism
是两个完全独立的,不相交的数据类型。 您可以粘合在一起,这些Either
作为另一个答案的建议,但他们不具有相同的类型,不能在同一个列表中包含在一起-这将没有意义。
您可能习惯于在haskell中完全不存在的子类型 - 我们依赖于和类型(如Either
或Shape
)之类的东西来允许值具有多个(标记的!)类型的值。 它与你习惯的不同。 在Haskell中,值和表达式只有一种类型 ,你不能说Sphere
(在你的第二个例子中)是一个Shape
因为它不是! 这是一个Sphere
,没有别的。 在第一个示例中, Sphere
是定义的Shape
,因此[Shape]
是有效类型。
我想你可能会对Haskell中的类型如何工作感到困惑。 Haskell使用参数多态和基于类的ad-hoc多态。 它没有像结构子类型那样的东西。 给予数据构造函数字段的名称与在那里声明的类型相关联; 它们不能在别处重复使用。 即使使用允许这种重用的GHC扩展,重用的名称也不相互关联。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.