具有上下文的Haskell数据类型

时间:2019-03-05 19:59:34

标签: haskell

我想为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编写的正确方法是什么?

2 个答案:

答案 0 :(得分:5)

噢。这个众所周知的问题是我的宠儿。正确的解决方案是使XYPoint类对于参数类型。标量参数成为关联的类型同义词,一切都很容易进行:

{-# 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

出于某些原因,大多数人还是喜欢将几何实体的类设为标量类型以外的参数,这不仅完全不必要,而且非几何,因为强调的是适当的几何形状取决于实际的基础分解。


实际上,我相当确定原因是什么:Edward Kmett决定在the 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

  • ax实际上是函数,因此不能将它们与y之类的数字相乘。我怀疑您只是在这里感到困惑,可能已经想出该怎么做-您需要将它们应用于sinA(“点”本身)以获取“ x”和“ y”“坐标”

所以实际上您已经很接近了,只需要注意编译器告诉您的内容即可。当您不熟悉Haskell时,GHC的错误消息可能看起来有些晦涩,但是经过一些实践,您很快就会发现它们(通常,尽管并不总是)很有帮助。