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

问题描述 投票:1回答:3

假设我有

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)

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

haskell typeclass
3个回答
3
投票

在面向对象的语言中,通常采用特定具体类的对象,比如说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。


3
投票

您可能希望能够让调用者通过您当前忽略的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

但是,为什么你想要做到这一点,我想知道。


2
投票

你在请求A。 Haskell的类型系统不像普遍使用的那样直接支持这些,但有两种常见的编码:

  • 包装纸 data A = A' Int | A'' Char selectA :: Int -> Maybe A selectA 0 = Just $ A' 0 selectA 1 = Just $ A'' '0' selectA _ = Nothing
  • 延续传球风格 existential type
© www.soinside.com 2019 - 2024. All rights reserved.