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) = ...
有没有办法让我定义一个形状列表,遍历列表,并在每个形状上调用绘图函数?以下代码将无法编译,因为列表元素的类型不同:
shapes = [(Circle 5 10), (Circle 20, 30), (Rectangle 10 15)]
我知道我正在以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)]
(我重命名了你的类,否则会有与数据类型的名称冲突,并且以这种方式命名似乎更自然).
这个解决方案优于涉及具有不同构造函数的单个数据类型的其他答案的优点是它是开放的 ; 您可以定义IsShape任何您喜欢的新实例.另一个答案的优点是它更具"功能性",并且在某些情况下封闭性可能是一个优势,因为它意味着客户确切地知道期待什么.
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]
正如Ganesh所说,你确实可以使用GADT来提高类型的安全性.但如果你不想(或不需要),这是我对此的看法:
如您所知,列表的所有元素都必须属于同一类型.拥有不同类型的元素列表并不是非常有用,因为这会丢弃您的类型信息.
但是,在这种情况下,由于您希望丢弃类型信息(您只对值的可绘制部分感兴趣),您建议将值的类型更改为可绘制的内容.
type Drawable = IO ()
shapes :: [Drawable]
shapes = [draw (Circle 5 10), draw (Circle 20 30), draw (Rectangle 10 15)]
据推测,你的实际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)]
这里,shape函数通过简单地调用类中的所有函数将Shape a值转换为ShapeResult值Shape.由于懒惰,在您需要之前,实际上不会计算任何值.
说实话,我认为我实际上不会使用这样的结构.我会使用Drawable上面的-method,或者如果需要更通用的解决方案,请使用GADT.话虽如此,这是一个有趣的练习.
一种方法是使用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 = ...
}