假设我有
class A a where
a :: a
data A' = A' Int
instance A A' where
a = A' 0
selectA :: A a => Int -> Maybe a
selectA _ = Just (A' 0)
如您所见,selectA
应该选择一些类型实现A
的值。注意:a
的类型不必是a
,它可能是其他内容,例如a -> Char
。无论如何,考虑到A'
是班级A
的成员,我希望selectA
的这种实现能够奏效。但是,编译它会产生以下错误:
• Couldn't match type ‘a’ with ‘A'’
‘a’ is a rigid type variable bound by
the type signature for:
selectA :: forall a. A a => Int -> Maybe a
at --
Expected type: Maybe a
Actual type: Maybe A'
• In the expression: Just (A' 0)
In an equation for ‘selectA’: selectA _ = Just (A' 0)
• Relevant bindings include
selectA :: Int -> Maybe a (bound at --)
我不确定的是,这个问题来自于selectA
的来电者能够确定a
的确切类型。我只对获得实现A
的某事感兴趣。如果可能的话,这将解决问题:
selectA :: Int -> Maybe (A a => a)
不幸的是,这是不可能的。我如何获得类似的功能?
答案 0 :(得分:3)
您可能希望能够让调用者通过您当前忽略的selectA
参数选择Int
的返回类型。
类似的东西:
data A' = A' Int
data A'' = A'' Char
instance A A' where
a = A' 0
instance A A'' where
a = A'' '0'
selectA :: (A a) => Int -> Maybe a
selectA 0 = Just (A' 0)
selectA 1 = Just (A'' '0')
selectA _ = Nothing
(注意:这不会编译 - 只是“Pseudo Haskell”来描绘我想要实现的目标)
如果你不想忽视这一点。但是,如果您这样做,请考虑不使用类型类A
,而是使用具有多个构造函数的数据类型A
。
data A = A' Int | A'' Char
selectA :: Int -> Maybe A
selectA 0 = Just $ A' 0
selectA 1 = Just $ A'' '0'
selectA _ = Nothing
为什么你想要做到这一点,我想知道。
答案 1 :(得分:3)
In object oriented languages, it's very common to take an object of a specific concrete class, like say a Circle
, and "upcast" it to an abstract superclass (like Shape
) or an interface (like Drawable
) where it will be manipulated using a restricted set of methods (e.g., extent()
, draw()
, etc.) applicable to that superclass / interface.
If you are trying to take this pattern and translate it into Haskell typeclasses, you are probably going to end up with a horrible design.
But who am I to stop you?
You can more or less accomplish what you want with GADTs:
{-# LANGUAGE GADTs #-}
Given the following class with two instances:
class A a where
name :: a -> String
data A1 = A1 Int
data A2 = A2 Double
instance A A1 where name _ = "A1"
instance A A2 where name _ = "A2"
you can define a GADT that will wrap any type with an A
instance:
data SomeA where
SomeA :: A a => a -> SomeA
together with an instance to bridge the methods to the wrapped type:
instance A SomeA where
name (SomeA x) = name x
and now selectA
can return any (wrapped) type that's an instance of A
:
selectA :: Int -> Maybe SomeA
selectA 1 = Just (SomeA (A1 0))
selectA 2 = Just (SomeA (A2 0))
which can be used through the methods for typeclass A
:
test :: IO ()
test = do putStrLn $ name (fromJust (selectA 1))
putStrLn $ name (fromJust (selectA 2))
Note that the GADT above is equivalent to the existential type being discussed in the comments:
data SomeA = forall a . A a => SomeA a
Using this syntax requires enabling the ExistentialQuantification
extentsion instead of GADTs
.
答案 2 :(得分:2)
您正在申请existential type。 Haskell的类型系统不像普遍使用的那样直接支持这些,但有两种常见的编码:
包装器
data SomeA where
SomeA :: A a => a -> SomeA
selectA :: Int -> SomeA
selectA _ = SomeA (A' 0)
延续传球风格
selectA :: Int -> (forall a. A a => a -> z) -> z
selectA _ c = c (A' 0)