简体   繁体   English

带有上下文的Haskell数据类型

[英]Haskell Data types with context

I want to write base implementation for Vertex. 我想为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

I have to create instance of typeclass Vertex with implementation of Floating typeclass in Point type parameter. 我必须创建类型类Vertex的instance ,并在Point类型参数中实现Floating类型类。 If i implement it like instance (Floating a) => Vertex Point a where i get: 如果我实现它像instance (Floating a) => Vertex Point a where我得到的地方:

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

What is the correct way to write it in Haskell? 在Haskell中编写它的正确方法是什么?

Aww. 噢。 This well-known problem is a pet peeve of mine. 这个众所周知的问题是我的一个宠儿。 The correct™ solution is to make the XY and Point classes not for parametric types. correct™解决方案是使XYPoint不适用于参数类型。 The scalar argument becomes an associated type synonym, and everything works easily: 标量参数成为关联类型的同义词,一切都很容易:

{-# 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 In fact you could even consider simplifying this to always use the same component type, since you'll likely never need anything else: 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 

With the non-parametric form, you can now easily add a number-type constraint exactly where it's needed, namely in the instance Vertex Point instance: 使用非参数形式,您现在可以轻松地将数字类型约束精确地添加到需要的位置,即在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

For some reason , most people however prefer to make classes for geometric entities parametric over the scalar type, which is not only completely unnecessary but also un-geometric , because proper geometry is emphatically not depended of an actual basis decomposition. 出于某种原因 ,大多数人更喜欢为标量类型参数化的几何实体制作类,这不仅是完全不必要的,而且也是非几何的 ,因为正确的几何体强调依赖于实际的基础分解。


Actually I'm fairly certain what the reason is: Edward Kmett's decision to use parameterised types in the linear library . 其实我很确定原因是什么:Edward Kmett决定linear库中使用参数化类型。 He should have known better, especially since Conal Elliott's vector-space library , which does it the right way, has been around for longer already. 他本应该知道的更好,特别是因为Conal Elliott的vector-space以正确的方式完成,已经存在了很长时间。

The following version is corrected so that it compiles: 以下版本已更正,以便编译:

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

There were only 2 changes needed, in fact: 事实上只需要进行2次更改:

  • I have added type constraints on a for the methods of the XY class. 我已经添加类型约束a对的方法XY类。 Otherwise, you can't use functions such as + which you have in the implementation of the instance for Point . 否则,您不能在Point的实例实现中使用+等函数。 (GHC actually makes this exact suggestion in one of the error messages it throws when trying to compile your version.) Note that these have to go on the class, not the instance, because the instance declaration makes no mention of the type a (even though the implementation does). (GHC实际上在尝试编译你的版本时会在它抛出的一条错误消息中提出这个确切的建议。)注意,这些必须在类而不是实例上进行,因为实例声明没有提到类型a (甚至虽然实施确实)。 If you don't put the constraints in the class then the methods are expected to work for all possible types a . 如果你没有在类中放置约束,那么这些方法应该适用于所有可能的类型a

  • x and y are in fact functions, so you can't multiply them with numbers like sinA . xy实际上是函数,所以你不能将它们与sinA这样的数字sinA I suspect you just got confused here and could have figured out what to do - you needed to apply them to the xy (the "point" itself) to get the "x" and "y" "co-ordinates". 我怀疑你刚刚在这里感到困惑,并且可以想出要做什么 - 你需要将它们应用到xy (“点”本身)以获得“x”和“y”“坐标”。

So actually you were pretty close, and just needed to pay attention to what the compiler was telling you. 所以实际上你非常接近,只需要注意编译器告诉你的内容。 GHC's error messages can seem a bit obscure when you're new to Haskell, but with a bit of practice you soon see that they're (often, although not always) quite helpful. 当你刚接触Haskell时,GHC的错误信息看起来有点模糊,但通过一些练习,你很快就会发现它们(通常,虽然并非总是)非常有用。

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

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