解决多参数类型类的实例歧义问题

8

我正在开发一个专业的数字数据处理库,但是我遇到了一个错误,不知道如何解决。我认为先展示一个例子,然后再解释我的问题会更容易一些。另外,对于奇怪的名称,我需要进行混淆以符合法律要求。

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

因为编译器无法确定在MyList中使用哪个特定的MyNum实例。对于myAdd可以工作,因为可以轻松推导出MyNum的返回类型,但是对于myLessThan则不行。我想使用这个类型类,以便我可以轻松地添加细粒度错误处理,并且因为我的实际代码具有+、-、*、/、<、<=、>和>=等等运算,我想为MyNum Double MyList MyListMyNum MyList MyList MyList等制作实例,以及NamedList的类似实例。除非有更简单的方法来做到这一点,否则这样做是为了拥有多态的可交换运算符。

然而,我无法确定在第二个实例中为myLessThan添加什么类型签名,以便它可以知道要使用哪个实例。我知道一种解决方案是将算术和比较运算符拆分为两个单独的类型类,但如果可能的话,我想避免这样做。

2个回答

11

您可以使用函数依赖来指定“retab唯一确定”。

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

这样让类型检查器知道它可以根据参数中的ab选择正确的实例定义:

myLessThan (NamedList n l) x = myLessThan l x

编译器现在会报错,如果您定义了另一个实例,其具有相同的abret不同,例如:

现在编译器将检查并报告此类错误。

instance MyNum MyList Double SomeOtherType where

这是我之前在Haskell中不熟悉的构造,正好是我需要的,谢谢! - bheklilr
2
@bheklilr 很高兴我能帮到你。你可能还想学习一下TypeFamilies,它们在某些方面更易于使用且更强大。 - jberryman

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类型从类参数移到了关联类型 Ret 中。
这是一种使用TypeFamily的方式,表示从类参数abRet的函数。


我喜欢它从MyNum类中删除了第三个显式类型参数。但是,有人有关于哪种更好的性能信息吗?如果这意味着代码更混乱但运行更快,我不介意;或者如果类型族更有效率,我宁愿使用那个解决方案。 - bheklilr
1
据我所知,这两种解决方案的性能是相等的。您可以检查已编译的核心以查看是否有任何差异 - 但在这种情况下,我不认为会有差异。 - ocharles
@ocharles 我会尽快尝试对其进行性能分析,并在稍后发布我的结果更新。 - bheklilr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接