我想创建两个类型类,A
和B
,其中A
是B
的超类。 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
的情况下实现这种子类型化效果?
答案 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
这样的类,它们有一个额外的类型构造函数,将类型包装在类型签名中,它与角色系统冲突);但是,当它起作用时,它会非常整洁。