繁体   English   中英

带有上下文的Haskell数据类型

[英]Haskell Data types with context

我想为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

我必须创建类型类Vertex的instance ,并在Point类型参数中实现Floating类型类。 如果我实现它像instance (Floating a) => Vertex Point a where我得到的地方:

 Expected kind ‘* -> Constraint’,
        but ‘Vertex Point’ has kind ‘Constraint’

在Haskell中编写它的正确方法是什么?

噢。 这个众所周知的问题是我的一个宠儿。 correct™解决方案是使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

NB事实上,您甚至可以考虑将其简化为始终使用相同的组件类型,因为您可能永远不需要其他任何东西:

 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决定linear库中使用参数化类型。 他本应该知道的更好,特别是因为Conal Elliott的vector-space以正确的方式完成,已经存在了很长时间。

以下版本已更正,以便编译:

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

事实上只需要进行2次更改:

  • 我已经添加类型约束a对的方法XY类。 否则,您不能在Point的实例实现中使用+等函数。 (GHC实际上在尝试编译你的版本时会在它抛出的一条错误消息中提出这个确切的建议。)注意,这些必须在类而不是实例上进行,因为实例声明没有提到类型a (甚至虽然实施确实)。 如果你没有在类中放置约束,那么这些方法应该适用于所有可能的类型a

  • xy实际上是函数,所以你不能将它们与sinA这样的数字sinA 我怀疑你刚刚在这里感到困惑,并且可以想出要做什么 - 你需要将它们应用到xy (“点”本身)以获得“x”和“y”“坐标”。

所以实际上你非常接近,只需要注意编译器告诉你的内容。 当你刚接触Haskell时,GHC的错误信息看起来有点模糊,但通过一些练习,你很快就会发现它们(通常,虽然并非总是)非常有用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM