返回实现特定类型类的任何类型的值

时间:2017-12-18 01:48:50

标签: haskell typeclass

假设我有

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)

不幸的是,这是不可能的。我如何获得类似的功能?

3 个答案:

答案 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)