如何“扩展” Haskell中的类

Dav*_*ams 10 haskell typeclass

我想创建两个类型类,AB,其中A是的超类B。中定义的功能B足以实现中的功能A。然后,如果我有一个带有for fun :: (A thing) => ...实例的约束的函数,例如,我希望能够传递to 而不创建for 的重复实例。BIntIntfunAInt

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

这是我认为的样子:

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
Run Code Online (Sandbox Code Playgroud)

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

[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 ...
Run Code Online (Sandbox Code Playgroud)

并且在运行时,程序失败:

Foo: Foo.hs:11:10-19: No instance nor default method for class operation isEven
Run Code Online (Sandbox Code Playgroud)

如何在不将逻辑复制到的情况下实现这种子类型化效果instance IsEven

mel*_*ene 13

据我所知,标准Haskell最接近的是

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

instance DivisibleBy Int where
  divisibleBy a i = a `mod` i == 0
Run Code Online (Sandbox Code Playgroud)

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

您必须为要创建的每个类型重复此模式DivisibleBy

使用DefaultSignatures语言扩展,您还可以执行以下操作:

{-# 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
Run Code Online (Sandbox Code Playgroud)

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


Jos*_*ica 7

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

你需要DefaultSignatures扩展,使这项工作。打开它,然后将您的类更改为此:

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
Run Code Online (Sandbox Code Playgroud)


dup*_*ode 5

对于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
Run Code Online (Sandbox Code Playgroud)

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