我想为Vertex编写基本实现。
data Point a = Point a a
class XY c where
x :: c a -> a
y :: c a -> a
class XY c => Vertex c where
translate :: c a -> c a -> c a
scale :: a -> c a -> c a
rotate :: a -> c a -> c a
instance XY Point where
x (Point first second) = first
y (Point first second) = second
instance Vertex Point where
translate xy1 xy2 = Point (x xy1 + x xy2) (y xy1 + y xy2)
scale a xy = Point ((*a) $ x xy) ((*a) $ y xy)
rotate a xy = Point (x * cosA - y * sinA) (x * sinA + y * cosA) where
cosA = cos a
sinA = sin a
我必须使用Point类型参数中的Floating typeclass实现创建Vertex类型类的instance
。
如果我像instance (Floating a) => Vertex Point a where
那样实现,则会得到:
Expected kind ‘* -> Constraint’,
but ‘Vertex Point’ has kind ‘Constraint’
用Haskell编写的正确方法是什么?
答案 0 :(得分:5)
噢。这个众所周知的问题是我的宠儿。正确的解决方案是使XY
和Point
类对于参数类型不。标量参数成为关联的类型同义词,一切都很容易进行:
{-# LANGUAGE TypeFamilies #-}
class XY p where
type Component p :: *
x :: p -> Component p
y :: p -> Component p
class XY p => Vertex p where
translate :: p -> p -> p
scale :: Component p -> p -> p
rotate :: Component p -> p -> p
实际上,您甚至可以考虑简化此操作以始终使用相同的组件类型,因为您可能永远不需要其他任何东西:
class XY p where x :: p -> Double y :: p -> Double class XY p => Vertex p where translate :: p -> p -> p scale :: Double -> p -> p rotate :: Double -> p -> p
使用非参数形式,您现在可以轻松地在所需的确切位置(即在instance Vertex Point
实例中添加数字类型约束:
instance XY (Point a) where
type Component (Point a) = a
x (Point xc _) = xc
y (Point _ yc) = yc
instance Floating a => Vertex (Point a) where
translate xy1 xy2 = Point (x xy1 + x xy2) (y xy1 + y xy2)
scale a xy = Point ((*a) $ x xy) ((*a) $ y xy)
rotate a xy = Point (x * cosA - y * sinA) (x * sinA + y * cosA)
where cosA = cos a
sinA = sin a
出于某些原因†,大多数人还是喜欢将几何实体的类设为标量类型以外的参数,这不仅完全不必要,而且非几何,因为强调的是适当的几何形状不取决于实际的基础分解。
linear
library中使用参数化类型。他本应该了解得更多,尤其是因为以正确的方式做的Conal Elliott的vector-space
library已经存在了更长的时间。
答案 1 :(得分:2)
以下版本已得到更正,以便编译:
data Point a = Point a a
class XY c where
x :: c a -> a
y :: c a -> a
class XY c => Vertex c where
translate :: (Num a) => c a -> c a -> c a
scale :: (Num a) => a -> c a -> c a
rotate :: (Floating a) => a -> c a -> c a
instance XY Point where
x (Point first second) = first
y (Point first second) = second
instance Vertex Point where
translate xy1 xy2 = Point (x xy1 + x xy2) (y xy1 + y xy2)
scale a xy = Point ((*a) $ x xy) ((*a) $ y xy)
rotate a xy = Point ((x xy) * cosA - (y xy) * sinA) ((x xy) * sinA + (y xy) * cosA) where
cosA = cos a
sinA = sin a
实际上只需要进行两项更改:
我在a
类的方法上为XY
添加了类型约束。否则,您将无法使用+
的实例实现中使用的功能,例如Point
。 (GHC实际上是在尝试编译您的版本时抛出的错误消息之一中给出了确切的建议。)请注意,这些必须在类而不是实例上进行,因为实例声明没有提及类型{{1 }}(即使实现确实如此)。如果您不将约束放在类中,则这些方法将适用于所有个可能的类型a
。
a
和x
实际上是函数,因此不能将它们与y
之类的数字相乘。我怀疑您只是在这里感到困惑,可能已经想出该怎么做-您需要将它们应用于sinA
(“点”本身)以获取“ x”和“ y”“坐标”
所以实际上您已经很接近了,只需要注意编译器告诉您的内容即可。当您不熟悉Haskell时,GHC的错误消息可能看起来有些晦涩,但是经过一些实践,您很快就会发现它们(通常,尽管并不总是)很有帮助。