3

我在 Haskell 中使用以下简单代码时遇到了麻烦:

import Prelude hiding (cycle).

class ICycle a where
    cycle :: a -> a

instance ICycle [a] where
    cycle [] = []
    cycle (x:xs) = xs ++ [x]

instance ICycle Bool where
    cycle True = False
    cycle False = True

instance Num a => ICycle a where
    cycle n = n+1

main = do
    print $ cycle $ [1,2,3]
    print $ cycle $ True
    print $ cycle $ 42

这里前两个实例声明按预期工作,但第三个实例声明根据标志组合触发各种错误。

我知道它Num a不短ICycle a,因此编译器无法完成类型检查。在示例中,我已经看到通过使右侧成为更大的术语或首先将感兴趣的类声明为其他类的子类来规避这一点。相反,在这里,我基本上想将现有类声明为新类的子类。

我想知道是否有人反对这种类型类的使用。或者,如果有一个自然的解决方案。

4

2 回答 2

3

对于这个特定的例子,我认为你最好使用 anewtype来包装实例:

{-# LANGUAGE GeneralizedNewtypeDeriving #-}
import Prelude hiding (cycle)

class ICycle a where
    cycle :: a -> a

newtype Succ a = Succ { runSucc :: a }
  deriving (Num, Eq, Ord, Bounded, Enum, Show, Read)

newtype Pred a = Pred { runPred :: a }
  deriving (Num, Eq, Ord, Bounded, Enum, Show, Read)

instance Enum a => ICycle (Succ a) where
    cycle = Succ . succ . runSucc

instance Enum a => ICycle (Pred a) where
    cycle = Pred . pred . runPred

main = do
    print $ cycle $ (42 :: Succ Int)
    print $ cycle $ (42 :: Pred Int)

有多种方法可以循环遍历数字 - 通过 succ、通过 pred、通过加倍、通过减半。为实例使用 a 的优势newtype(使 RHS “更大”,正如您在问题中指出的那样)是它让我们拥有所有这些。

标准库对ProductSumfor做同样的事情Monoid

换一种方式来看,如果可以为 定义一个新的超类Num,为 的所有实例添加一个默认实现 Num,那么您将放弃所有这些实现的选择。可能以一种没有意义的方式。

于 2015-04-29T01:29:26.040 回答
2

根据 Haskell 报告 2010,第 4 章,声明和绑定,要定义为实例的事物需要是类型构造函数。因此,

instance Num a => ICycle a where
    ...

无效,因为a是类型变量,而不是类型构造函数。

因此,不幸的是,有效的方法是按类型键入。不得不说:

instance ICycle Int where
    ...

instance ICycle Double where
    ...

等等。

于 2015-04-29T02:36:05.173 回答