解决多参数类型类的模糊实例

时间:2013-08-26 21:52:46

标签: haskell ghc

我正在开发一个专门的数值数据处理库,我遇到了一个错误,我无法弄清楚如何修复。我认为首先展示一个例子然后解释我的问题会更容易。我也为这些陌生的名字道歉,我不得不为法律目的进行混淆。

{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances     #-}

data MyError = MyError String deriving (Eq, Show)

data MyList    = MyList [Double] deriving (Eq, Show)
data NamedList = NamedList String MyList deriving (Eq, Show)

class MyNum a b ret where
    myAdd       :: a -> b -> Either MyError ret
    myLessThan  :: a -> b -> Either MyError Bool

instance MyNum MyList Double MyList where
    myAdd (MyList xs) x = Right $ MyList $ map (+x) xs
    myLessThan (MyList xs) x = Right $ all (< x) xs

instance MyNum NamedList Double NamedList where
    myAdd (NamedList n l) x = fmap (NamedList n) $ myAdd l x
    myLessThan (NamedList n l) x = myLessThan l x

如果我尝试编译它,我会收到错误

No instance for (MyNum MyList Double ret0)
  arising from a use of `myLessThan'
The type variable `ret0' is ambiguous
Possible fix: add a type signature that fixes these type variable(s)
Note: there is a potential instance available:
  instance MyNum MyList Double MyList
    -- Defined at testing_instances.hs:13:10
Possible fix:
  add an instance declaration for (MyNum MyList Double ret0)
In the expression: myLessThan l x
In an equation for `myLessThan':
    myLessThan (NamedList n l) x = myLessThan l x
In the instance declaration for `MyNum NamedList Double NamedList'

因为编译器无法确定MyNum用于MyList的具体实例。它适用于myAdd,因为MyNum的返回类型很容易派生,但它无法计算myLessThan。我想使用这个类型类,以便我可以轻松地在整个过程中添加细粒度的错误处理,因为我的实际代码具有+, - ,*,/,&lt;,&lt; =,&gt;和&gt; = ,我想为MyNum Double MyList MyListMyNum MyList MyList MyList创建一个实例,为NamedList创建类似的实例。除非有更简单的方法,否则我可以拥有多态交换运算符。

但是,我无法弄清楚要为第二个实例添加到myLessThan的类型签名,以便它可以知道要使用的实例。我知道一种解决方案是将算术运算符和比较运算符分成两个独立的类型类,但我想尽可能避免这样做。

2 个答案:

答案 0 :(得分:11)

您可以使用functional dependencies指定“retab唯一确定”。

...
{-# LANGUAGE FunctionalDependencies #-}
...
class MyNum a b ret | a b -> ret where
...

这让typechecker知道它可以选择正确的实例定义,只知道你的参数中的ab

myLessThan (NamedList n l) x = myLessThan l x

如果你定义一个具有相同ab但是ret不同的其他实例,编译器现在会抱怨,例如

instance MyNum MyList Double SomeOtherType where

答案 1 :(得分:4)

正如jberryman所说,你可以使用TypeFamilies。这就是:

-{-# LANGUAGE FlexibleInstances     #-}
+{-# LANGUAGE TypeFamilies #-}

-class MyNum a b ret where
-    myAdd       :: a -> b -> Either MyError ret
+class MyNum a b where
+    type Ret a b
+    myAdd       :: a -> b -> Either MyError (Ret a b)

-instance MyNum MyList Double MyList where
+instance MyNum MyList Double where
+    type Ret MyList Double = MyList

-instance MyNum NamedList Double NamedList where
+instance MyNum NamedList Double where
+    type Ret NamedList Double = NamedList

我刚刚将ret类型从类参数移至associated type Ret 这是一种TypeFamily - 表示类参数abRet的函数的方法。