haskell多态性和列表

Cli*_*ler 17 polymorphism haskell list

假设我有以下内容:

class Shape a where
    draw a :: a -> IO ()

data Rectangle = Rectangle Int Int

instance Shape Rectangle where
    draw (Rectangle length width) = ...

data Circle = Circle Int Int

instance Shape Circle where
    draw (Circle center radius) = ...
Run Code Online (Sandbox Code Playgroud)

有没有办法让我定义一个形状列表,遍历列表,并在每个形状上调用绘图函数?以下代码将无法编译,因为列表元素的类型不同:

shapes = [(Circle 5 10), (Circle 20, 30), (Rectangle 10 15)]
Run Code Online (Sandbox Code Playgroud)

我知道我正在以OO的方式思考并尝试将其应用于Haskell,这可能不是最好的方法.对于需要处理不同类型对象集合的程序,最好的Haskell方法是什么?

GS *_*ica 22

如果你确实需要这样做,那么使用存在主义:

{-# LANGUAGE GADTs #-}


class IsShape a where
    draw :: a -> IO ()

data Rectangle = Rectangle Int Int

instance IsShape Rectangle where
    draw (Rectangle length width) = ...

data Circle = Circle Int Int

instance IsShape Circle where
    draw (Circle center radius) = ...

data Shape where
    Shape :: IsShape a => a -> Shape

shapes = [Shape (Circle 5 10), Shape (Circle 20 30), Shape (Rectangle 10 15)]
Run Code Online (Sandbox Code Playgroud)

(我重命名了你的类,否则会有与数据类型的名称冲突,并且以这种方式命名似乎更自然).

这个解决方案优于涉及具有不同构造函数的单个数据类型的其他答案的优点是它是开放的 ; 您可以定义IsShape任何您喜欢的新实例.另一个答案的优点是它更具"功能性",并且在某些情况下封闭性可能是一个优势,因为它意味着客户确切地知道期待什么.

  • 值得注意的是,对于未来的读者来说,这种方法的缺点是从"形状"容器中获取圆形和矩形的代码将无法使用*任何*其他属性,而不是"IsShape"实例中给出的属性.你无法得到坐标,无法判断它是一个"圆"还是一个"矩形",并且不能调用任何其他特定于圆形或矩形的函数,而不是任何形状.这是Ganesh所讨论的开放性的一个基本结果(并且当你感到被迫"低迷"时,会出现在静态类型的OO编程中). (2认同)

dav*_*420 13

考虑使用单一类型而不是单独的类型和类型类.

data Shape = Rectangle Int Int
           | Circle Int Int

draw (Rectangle length width) = ...
draw (Circle center radius)   = ...

shapes = [Circle 5 10, Circle 20 30, Rectangle 10 15]
Run Code Online (Sandbox Code Playgroud)

  • 虽然这有效,但我一直在寻找一种解决方案,允许在其他地方定义新的数据类型,以便处理形状列表的代码不需要知道每种类型的形状. (6认同)

Tom*_*rst 5

正如Ganesh所说,你确实可以使用GADT来提高类型的安全性.但如果你不想(或不需要),这是我对此的看法:

如您所知,列表的所有元素都必须属于同一类型.拥有不同类型的元素列表并不是非常有用,因为这会丢弃您的类型信息.

但是,在这种情况下,由于您希望丢弃类型信息(您只对值的可绘制部分感兴趣),您建议将值的类型更改为可绘制的内容.

type Drawable = IO ()

shapes :: [Drawable]
shapes = [draw (Circle 5 10), draw (Circle 20 30), draw (Rectangle 10 15)]
Run Code Online (Sandbox Code Playgroud)

据推测,你的实际Drawable将会比仅仅更有趣IO ()(可能是这样的东西MaxWidth -> IO ()).

而且,由于懒惰的评估,在您强制使用类似的列表之前,不会绘制实际值sequence_.所以你不必担心副作用(但你可能已经从类型中看到了shapes).


只是为了完整(并将我的评论纳入此答案):这是一个更通用的实现,如果Shape有更多功能,则非常有用:

type MaxWith = Int

class Shape a where
    draw :: a -> MaxWidth -> IO ()
    size :: a -> Int

type ShapeResult = (MaxWidth -> IO (), Int)

shape :: (Shape a) => a -> ShapeResult
shape x = (draw x, size x)

shapes :: [ShapeResult]
shapes = [shape (Circle 5 10), shape (Circle 20 30), shape (Rectangle 10 15)]
Run Code Online (Sandbox Code Playgroud)

这里,shape函数通过简单地调用类中的所有函数将Shape a值转换为ShapeResultShape.由于懒惰,在您需要之前,实际上不会计算任何值.

说实话,我认为我实际上不会使用这样的结构.我会使用Drawable上面的-method,或者如果需要更通用的解决方案,请使用GADT.话虽如此,这是一个有趣的练习.


yai*_*chu 5

一种方法是使用vtables:

data Shape = Shape {
  draw :: IO (),
  etc :: ...
}

rectangle :: Int -> Int -> Shape
rectangle len width = Shape {
  draw = ...,
  etc = ...
}

circle :: Int -> Int -> Shape
circle center radius = Shape {
  draw = ...,
  etc = ...
}
Run Code Online (Sandbox Code Playgroud)