假设我有
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)
不幸的是,这是不可能的。我如何获得类似的功能?
在面向对象的语言中,通常采用特定具体类的对象,比如说Circle
,并将其“向上”到抽象超类(如Shape
)或接口(如Drawable
),在那里它将被操作使用适用于该超类/接口的有限方法集(例如,extent()
,draw()
等)。
如果你试图采用这种模式并将其转换为Haskell类型类,那么你可能最终会遇到一个可怕的设计。
但我是谁阻止你?
您可以或多或少地用GADT完成您想要的任务:
{-# LANGUAGE GADTs #-}
给定以下具有两个实例的类:
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"
您可以定义一个GADT,它将使用A
实例包装任何类型:
data SomeA where
SomeA :: A a => a -> SomeA
与一个实例一起将方法桥接到包装类型:
instance A SomeA where
name (SomeA x) = name x
现在selectA
可以返回任何(包装)类型,这是A
的一个实例:
selectA :: Int -> Maybe SomeA
selectA 1 = Just (SomeA (A1 0))
selectA 2 = Just (SomeA (A2 0))
可以通过类型类A
的方法使用:
test :: IO ()
test = do putStrLn $ name (fromJust (selectA 1))
putStrLn $ name (fromJust (selectA 2))
请注意,上面的GADT等同于评论中讨论的存在类型:
data SomeA = forall a . A a => SomeA a
使用此语法需要启用qazxsw poi扩展而不是qazxsw poi。
您可能希望能够让调用者通过您当前忽略的ExistentialQuantification
参数选择GADTs
的返回类型。
就像是:
selectA
(注意:这不会编译 - 它只是“Pseudo Haskell”来描绘我认为你想要实现的目标)
如果你不想忽视这一点。但是,如果您这样做,请考虑不使用类型类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
。
A
但是,为什么你想要做到这一点,我想知道。
你在请求A
。 Haskell的类型系统不像普遍使用的那样直接支持这些,但有两种常见的编码:
data A = A' Int | A'' Char
selectA :: Int -> Maybe A
selectA 0 = Just $ A' 0
selectA 1 = Just $ A'' '0'
selectA _ = Nothing