繁体   English   中英

为什么Haskell允许使用Shape列表,但没有Square或Circle或Triangle列表

[英]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 -> Stringname :: Prism -> String ,如果不使用类型类,则不允许这样做。

我重命名了SphereTagPrismTag ,以明确Sphere类型和数据构造函数SphereTag之间的区别

一种类型

在第一个例子中

data Shape
   = SphereShape { name :: String, position :: Vector3D, radius :: Double }
   | PrismShape { name :: String, position :: Vector3D, dimensions :: Vector3D }

一种类型,所以你可以制作[Shape]

要么 - 标记联盟

结合两种类型的经典方法是使用Either,它使用LeftRight标记来自两种类型的数据:

type PrismOrSphere = Either Prism Sphere

myList = [Left (SphereTag "this" ...), Right (PrismTag "that" ....), ....]

但你的自定义Shape类型可能会更好。

来自OOP的建议

尽量不要将OOP教学示例重新用作函数式编程示例。 OOP示例旨在首先教授OO原则,然后编程编程,它们的设计非常适合开发OO和命令式思维。

这就像试图通过在空车停车场学习驾驶飞机一样。 在飞机上停车很慢,但这可能是你开车时学到的第一件事。

如果你坚持通过试图复制你的驾驶课程来学习飞行,你会发现你的飞机是一种非常不方便的汽车,不适合很多道路。

您应该使用一套精心编写的示例来教授函数式编程。 我推荐Learn You a Haskell for Great Good ,它包含web和dead tree版本。

OOP与FP的多态性

在面向对象编程中,您通常称为多态的是使用超类实现的。 您可以拥有由Shape的子类型组成的ShapeList或由管理器和清理器组成的EmployeeList,但是在传统的OOP中,您需要编写不同但相似的代码来实现每个方法的.sort方法。 我们可以称之为这种亚型多态性。 它与从泛型中获得的多态性不同,在泛型中您可以编写单个方法来处理任何类型。

在函数式编程中,你通常称之为多态的是通过对数据类型完全不可知来实现的,因此你可以编写一个函数reverse :: [a] -> [a] ,它将适用于任何可能的Shapes列表或者Spheres或Employees等等,更像是泛型但没有运行时类型的数据开销(请参阅Haskell擦除类型吗? )。 我们可以称之为参数多态。 它与从类型类中获得的多态性不同,在类型类中,允许多个类型具有相同的命名函数。

因为在ex1中, SpherePrism不是类型而是构造函数。 它们都是相同类型的Shape 因此,您可以创建[Shape]列表。 而且,由于它们不是类型, [Sphere]甚至没有意义,因为Sphere不在类型命名空间中。 我不知道这是否令你感到困惑,但是当我开始学习Haskell时,我混淆了构造函数和OO子类。 他们是不同的。 在那种情况下( ex1 )你不能有这样的事情:

radius :: Sphere -> Double

因为Sphere不是一种类型。 Sphere是一个返回Shape的函数。

现在,在ex2中SpherePrism是你所拥有的类型

  • 球体列表[Sphere]
  • 一系列棱镜[Prim]
  • AndrewC [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]类型的列表,它可以包含SpherePrism 例:

λ> let a = Sphere "sph1" 2.2 3.4
λ> let b = Prism "prism" 3.4 4.8
λ> let c = [a,b]
λ> :t c
c :: [Shape]

在上面的例子中,我假设Vector3DDouble类型。

如果我理解你,听起来你理解的是数据类型

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” - 一种数据类型,可以有多种形式之一,在本例中为SpherePrism 你可以创建一个Shape的“多态列表”,因为从本质上讲, 数据类型本身就是多态的 Shape可以有两种不同类型的值。

在第二个示例中, SpherePrism是两个完全独立的,不相交的数据类型。 您可以粘合在一起,这些Either作为另一个答案的建议,但他们不具有相同的类型,不能在同一个列表中包含在一起-这将没有意义。

您可能习惯于在haskell中完全不存在的子类型 - 我们依赖于和类型(如EitherShape )之类的东西来允许值具有多个(标记的!)类型的值。 它与你习惯的不同。 在Haskell中,值和表达式只有一种类型 ,你不能说Sphere (在你的第二个例子中)是一个Shape因为它不是! 这是一个Sphere ,没有别的。 在第一个示例中, Sphere是定义的Shape ,因此[Shape]是有效类型。

我想你可能会对Haskell中的类型如何工作感到困惑。 Haskell使用参数多态和基于类的ad-hoc多态。 它没有像结构子类型那样的东西。 给予数据构造函数字段的名称与在那里声明的类型相关联; 它们不能在别处重复使用。 即使使用允许这种重用的GHC扩展,重用的名称也不相互关联。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM