我是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并使用不同名称的成员。做这种事情的标准方法是什么?
答案 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)
?