不同类型的列表?

Arl*_*len 39 haskell

data Plane = Plane { point :: Point, normal :: Vector Double }
data Sphere = Sphere { center :: Point, radius :: Double }

class Shape s where
    intersect :: s -> Ray -> Maybe Point
    surfaceNormal :: s -> Point -> Vector Double
Run Code Online (Sandbox Code Playgroud)

我也做了两个PlaneSphere实例Shape.

我试图将球体和平面存储在同一个列表中,但它不起作用.据我所知,它不应该工作,因为SpherePlane两种不同的类型,但他们的两个实例Shape,所以它不应该工作?如何在列表中存储形状和平面?

shapes :: (Shape t) => [t]
shapes = [ Sphere { center = Point [0, 0, 0], radius = 2.0 },
         Plane { point = Point [1, 2, 1], normal = 3 |> [0.5, 0.6, 0.2] }
         ]
Run Code Online (Sandbox Code Playgroud)

luq*_*qui 54

这个问题代表了面向对象和功能性思维之间的转折点.有时甚至复杂的Haskeller仍处于这种心理转变中,他们的设计常常属于托马斯的答案中提到的存在类型模式.

这个问题的一个功能性解决方案涉及将类型类转换为数据类型(通常一旦完成,对类型类的需求就消失了):

data Shape = Shape {
    intersect :: Ray -> Maybe Point,
    surfaceNormal :: Point -> Vector Double
}
Run Code Online (Sandbox Code Playgroud)

现在您可以轻松构建一个Shapes 列表,因为它是一个单形类型.因为Haskell不支持向下转换,所以通过消除Planes和Spheres 之间的代表性区别不会丢失任何信息.特定数据类型成为构造Shapes的函数:

plane :: Point -> Vector Double -> Shape
sphere :: Point -> Double -> Shape
Run Code Online (Sandbox Code Playgroud)

如果您无法捕获有关Shape数据类型中的形状的所有信息,则可以使用代数数据类型枚举案例,如Thomas所建议的那样.但如果可能,我会建议不要这样做; 相反,尝试找到您需要的形状的基本特征,而不是仅仅列出示例.

  • 很好地说,我们不应该将Type类视为OO继承或接口,并将它们视为子类型.它们只是一种类型可以支持的一组函数 (4认同)

Tho*_*son 26

你正在寻找一个异构列表,大多数Haskeller并不特别喜欢它,即使他们在第一次学习Haskell时问自己同样的问题.

你写:

shapes :: (Shape t) => [t]
Run Code Online (Sandbox Code Playgroud)

这说明列表有类型t,所有这些都是相同的,恰好是一个Shape(相同的形状!).换句话说 - 不,它不应该如何工作.

处理它的两种常用方法(首先是Haskell 98方式,然后是我不推荐的第二种方式):

使用新类型静态联合感兴趣的子类型:

data Foo = F deriving Show
data Bar = B deriving Show

data Contain = CFoo Foo | CBar Bar deriving Show
stuffExplicit :: [Contain]
stuffExplicit = [CFoo F, CBar B]

main = print stuffExplicit
Run Code Online (Sandbox Code Playgroud)

这很好看,因为它是直接的,你不会丢失任何有关列表中包含的内容的信息.您可以确定第一个元素是a Foo,第二个元素是a Bar.正如您可能已经意识到的那样,缺点是您必须通过创建新的Contain类型构造函数来显式添加每个组件类型.如果这是不可取的,那么继续阅读.

使用存在类型:另一种解决方案涉及丢失有关元素的信息 - 您只需保留元素属于特定类的知识.因此,您只能在列表元素上使用该类中的操作.例如,下面只会记住类中的元素Show,因此您可以对元素执行的唯一操作是使用多态的函数Show:

data AnyShow = forall s. Show s => AS s

showIt (AS s) = show s

stuffAnyShow :: [AnyShow]
stuffAnyShow = [AS F, AS B]

main = print (map showIt stuffAnyShow)
Run Code Online (Sandbox Code Playgroud)

这需要对Haskell语言进行一些扩展,即ExplicitForAllExistentialQuantification.我们必须showIt明确定义(使用模式匹配来解构AnyShow类型)因为您不能对使用存在量化的数据类型使用字段名称.

有更多的解决方案(希望另一个答案将使用Data.Dynamic- 如果没有人做,你有兴趣,然后阅读它,并随时发布任何阅读产生的问题).