Haskell: 约束中的非类型变量参数

Haskell: Non type-variable argument in the constraint

我创建了一些函数来让 2D 几何体更加舒适。 在这个例子中我使用 Geom2D from CubicBezier package.

我程序的完整代码:https://gist.github.com/nskeip/3784d651ac646a67c5f246f048949af4

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FlexibleContexts #-}
import Geom2D

left :: (Num a) => Point a -> a -> Point a
left (Point x y) n = Point (x - n) y

right :: (Num a) => Point a -> a -> Point a
right (Point x y) n = Point (x + n) y

up :: (Num a) => Point a -> a -> Point a
up (Point x y) n = Point x (y - n)

down :: (Num a) => Point a -> a -> Point a
down (Point x y) n = Point x (y + n)

他们是这样工作的:

> (Point 0 0) `up` 10
Point 0.0 -10.0

其中Point定义如下:

data Point a = Point {
  pointX :: !a,
  pointY :: !a
  } deriving (Eq, Ord, Functor, Foldable, Traversable)

一切都很好,直到我想到:"Hey, it would be nice to make that functions (actualy, operators) work with thigs like Line - not only Point"

所以我声明了一个 class(为了简单起见,不使用 leftright):

class Num n => Moving p n where
  up'    :: n -> p -> p
  down'  :: n -> p -> p

  up'    n = down'  (-n)
  down'  n = up'    (-n)

Point a 数据类型的 Moving 实例:

instance Num a => Moving (Point a) a where
  up' n (Point x y) = Point x (y - n)

但是当我尝试使用它时,出现错误:

✗ ghci ./uno.hs 
GHCi, version 7.10.3: http://www.haskell.org/ghc/  :? for help
[1 of 1] Compiling Main             ( uno.hs, interpreted )
Ok, modules loaded: Main.
*Main> let p = Point { pointX = 0, pointY = 0 } 
*Main> up' 10 p

<interactive>:3:1:
    Non type-variable argument in the constraint: Moving (Point a) n
    (Use FlexibleContexts to permit this)
    When checking that ‘it’ has the inferred type
      it :: forall n a. (Num a, Moving (Point a) n) => Point a

让我很困惑的事情是:我把 FlexibleContexts pragma 放在头部的 pragma 列表中,但 ghcu 仍然建议我把它包括在内。

如何修复我的 class / 实例以使参数多态性正常工作? :)

And the thing that confuses me much: I put the FlexibleContexts pragma to the pragma listing in the head, but ghcu still suggest me to get it included.

这只会启用模块本身的扩展。要在 GHCi 中编写此代码,您需要在 GHCi 中启用扩展::set -XFlexibleContexts.

但这只是问题的一部分。看起来你的 class p 应该确定 n:你只能将 Point a 上下移动 a,对吗?但就目前而言,没有什么能阻止您定义更多 Moving (Point a) SomeOtherType 个实例,并且编译器不会假设您不会。因此推断类型中的 an 是完全不相关的,您希望它们相同。这可以通过添加 FunctionalDependencies extension 并将 class 声明更改为 say

来解决
class Num n => Moving p n | p -> n where

这意味着不能有相同 p 和不同 n 的实例。

我认为这足以让它发挥作用。该代码仍然是不确定的,因为它允许任何数字 a,但默认规则将选择 Integer.