如何“扩展” Haskell中的类

时间:2019-07-06 04:52:32

标签: haskell typeclass

我想创建两个类型类,AB,其中AB的超类。 B中定义的功能足以实现A中的功能。然后,如果我有一个约束为fun :: (A thing) => ...的函数,例如B的一个Int实例,我希望能够将Int传递给{ {1}},而没有为fun创建重复的实例A

例如,假设我有一个类型类,可以检查值是否为“偶数”。然后,我有另一个类型类,可以检查某个值是否可以被某个数整除。第二种类型的类足够强大,可以在第一种类型中实现这些功能,任何仅需要“偶数检查”功能的功能都应能够接受具有“可除数”功能的参数。

这是我认为的样子:

Int

但是,在编译时会产生警告:

class IsEven a where
  isEven :: a -> Bool

class (IsEven a) => DivisibleBy a where
  divisibleBy :: a -> Int -> Bool
  isEven :: a -> Bool
  isEven a = divisibleBy a 2

printIsEven :: (IsEven a) => a -> IO ()
printIsEven a = putStrLn (show (IsEven.isEven a))

instance IsEven Int  -- I need to do this or I cannot create a DivisibleBy instance
instance DivisibleBy Int where
  divisibleBy a i = a `mod` i == 0

myint :: Int
myint = 2

main :: IO ()
main = printIsEven myint

在运行时,程序失败:

[2 of 2] Compiling Main             ( Foo.hs, Foo.o )

Foo.hs:11:10: warning: [-Wmissing-methods]
    • No explicit implementation for
        ‘IsEven.isEven’
    • In the instance declaration for ‘IsEven Int’
   |
11 | instance IsEven Int
   |          ^^^^^^^^^^
Linking Foo ...

如何在不将逻辑复制到Foo: Foo.hs:11:10-19: No instance nor default method for class operation isEven 的情况下实现这种子类型化效果?

3 个答案:

答案 0 :(得分:12)

据我所知,在标准Haskell中可以找到的最接近的是

instance IsEven Int where
  isEven n = n `divisibleBy` 2

instance DivisibleBy Int where
  divisibleBy a i = a `mod` i == 0

您不必重复逻辑(实际上,您可以根据isEven来实现divisibleBy),但是仍然需要提供一个明确的定义。

对于要成为DivisibleBy实例的每种类型,您都必须重复此模式。

使用DefaultSignatures language extension,您还可以执行以下操作:

{-# LANGUAGE DefaultSignatures #-}

class IsEven a where
  isEven :: a -> Bool
  default isEven :: (DivisibleBy a) => a -> Bool
  isEven n = n `divisibleBy` 2

class (IsEven a) => DivisibleBy a where
  divisibleBy :: a -> Int -> Bool

instance IsEven Int
instance DivisibleBy Int where
  divisibleBy a i = a `mod` i == 0

这会将默认实现移至类本身。现在,您确实可以只说instance IsEven Int而无需提供实例主体。缺点是现在IsEven必须了解DivisibleBy,并且您只能提供一个default实现。

答案 1 :(得分:6)

您不能在新类中重新定义方法,而该方法会影响旧类中的方法。如果要使方法像这样工作,则父类必须引用子类。

您需要the DefaultSignatures extension才能完成此工作。打开它,然后将您的课程更改为此:

class IsEven a where
  isEven :: a -> Bool
  default isEven :: DivisibleBy a => a -> Bool
  isEven a = divisibleBy a 2

class IsEven a => DivisibleBy a where
  divisibleBy :: a -> Int -> Bool

答案 2 :(得分:4)

对于GHC 8.6及更高版本,这也可以通过DerivingVia实现:

{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE GeneralisedNewtypeDeriving #-}
{-# LANGUAGE StandaloneDeriving #-}

-- Class definitions:

class IsEven a where
  isEven :: a -> Bool

-- Note that we don't need to have IsEven as a superclass.
class DivisibleBy a where
  divisibleBy :: a -> Int -> Bool


-- Boilerplate that only needs to be written once:

-- Boilerplate DivisibleBy instance generated with GeneralisedNewtypeDeriving.
newtype WrappedDivisibleBy a = WrapDivisibleBy { unwrapDivisibleBy :: a }
  deriving DivisibleBy

instance DivisibleBy a => IsEven (WrappedDivisibleBy a) where
  isEven n = n `divisibleBy` 2 


-- Instance example:

instance DivisibleBy Int where
  divisibleBy a i = a `mod` i == 0

-- Boilerplate IsEven instance generated with DerivingVia 
-- (and StandaloneDeriving, as we aren't defining Int here).
deriving via (WrappedDivisibleBy Int) instance IsEven Int

DerivingVia并非始终是一个选项(对于Traversable这样的类,它们有一个额外的类型构造函数,将类型包装在类型签名中,它与角色系统冲突);但是,当它起作用时,它会非常整洁。