创建相同类型类但不同类型的值列表

时间:2018-10-20 15:58:25

标签: haskell

我是Haskell的新手,正在尝试做一些我认为很容易的事情,但是我没有找到正确的方法。

我想要的是特定类型类的值列表,但是该类型类的类型不同。例如:

class Shape a where
  area :: a -> Double
  numVertices :: a -> Integer

data Triangle = Triangle {...}
data Square = Square {...}

instance Shape Triangle where ...  
instance Shape Square where ...

x = [Triangle (...), Square (...)]

我遇到了编译器错误,因为列表具有不同的类型。什么是在这里做我想做的正确方法?我唯一能想到的就是做这样的事情:

data WrappedShape = WrappedShape {
    getArea :: () -> Double
  , getNumVertices :: () -> Integer
}

wrap s = WrappedShape {
    getArea = \ () -> area s
  , getNumVertices = \ () -> vertices s
}

x = [wrap (Triangle (...)), wrap (Square (...))]

这可行,但是很繁琐,因为我必须两次有效地定义Shape并使用不同名称的成员。做这种事情的标准方法是什么?

2 个答案:

答案 0 :(得分:1)

如果只需要一些不同的形状,则可以将每个形状枚举为构造函数,这是一个示例:

data SomeShapes = Triangle {...}
                  | Square {...}

instance Shape SomeShapes where 
    area (Triangle x) = ...
    area (Square x) = ....

现在您可以将它们放在列表中,因为它们与SomeShapes类型相同

[Triangle {...}, Square {...}]

答案 1 :(得分:1)

您的包装类型可能是最好的主意。

需要注意的是,在像Haskell这样的惰性语言中,类型() -> T实际上就像普通的T一样工作。您可能想要延迟计算,并编写诸如let f = \ () -> 1+2之类的东西,直到使用参数f调用函数()时才执行加法。但是,let f = 1+2在其他表达式真正需要f之前就不会执行加法运算-这是懒惰。

所以,我们可以简单地使用

data WrappedShape = WrappedShape {
    getArea :: Double
  , getNumVertices :: Integer
}

wrap s = WrappedShape {
    getArea = area s
  , getNumVertices = vertices s
}

x = [wrap (Triangle (...)), wrap (Square (...))]

,然后再忘记传递():当我们访问列表元素时,将计算面积(顶点)(无论我们需要什么)。也就是说print (getArea (head x))将计算三角形的面积。

急切的语言中确实需要\ () -> ...技巧,但是在Haskell中,这是一种反模式。大致来说,在Haskell中,所有内容的顶部都有一个\ () -> ...,因此,不必添加其他内容。


这是您的问题的另一种解决方案,称为“现有类型”。但是,有时有时也会变成anti-pattern,因此,我不建议您轻易使用它。

它的工作原理如下

data WrappedShape = forall a. Shape a => WrappedShape a

x = [WrappedShape (Triangle ...), WrappedShape (Square ...)]

exampleUsage = case head x of WrappedShape s -> area s

当类型类具有许多方法时,这将更加方便,因为我们不必在包装的类型中编写很多字段。

该技术的主要缺点是它涉及更复杂的类型的机械,没有任何实际收益。我的意思是基本列表[(Double, Integer)]具有与[WrappedShape](存在性列表)相同的功能,那么为什么还要麻烦后者?

卢克·帕尔默wrote谈谈这种反模式。我完全不同意该职位,但我认为他确实有一些好处。

我没有明确的界线,可以在基本方法之上开始使用存在性,但是我会考虑以下因素:

  • 类型类有多少种方法?
  • 是否存在类型为a(与该类相关的类型)的方法,而不仅仅是作为参数?例如。方法foo :: a -> (String, a)