根据返回类型选择类型类

时间:2015-05-02 09:31:01

标签: haskell typeclass

我希望能够拥有一个函数,该实现将根据它的返回类型的手动类型规范选择类型类。

这是一个人为的例子:类型类和两个实例:

class ToString a where
  toString :: a -> String

instance ToString Double where
  toString = const "double"

instance ToString Int where
  toString = const "int"

我可以通过调用Int类型的toString来选择实例:

function :: String
function = toString (undefined :: Int)

到目前为止一切顺利。我想要做的是能够编写该函数,因此如果存在类型类,它将适用于任何a

function' :: (ToString a) => String
function' = toString (undefined :: a)

这里,function'没有编译,因为签名中的任何地方都没有提到a类型参数,并且在调用时无法指定类型类。

所以看起来唯一的选择是将有关a类型的类型信息传递给返回类型:

data Wrapper a = Wrapper {
  field :: String }

function'' :: (ToString a) => Wrapper a
function'' = Wrapper $ toString (undefined :: a)

showToString :: String
showToString = field (function'' :: Wrapper Int)

我定义一个Wrapper类型只是为了携带类型信息。在showToString中,我希望由于我指定了Wrapper的确切类型,因此类型检查器可以推断出a中的function''Int并选择Int类型类的ToString实例。

但现实并不符合我的希望,这是来自编译器的信息

  

无法推断(ToString a0)因使用`toString'

而产生

有没有办法,如何说服编译器,他可以在function''中选择正确的类型类,因为我通过类型声明:: Wrapper Int来指定它?

2 个答案:

答案 0 :(得分:3)

首先,我建议您使用Data.Tagged.Tagged代替自己的Wrapper类型,其目的恰恰就是这类内容。

除此之外,您需要打开-XScopedTypeVariables扩展名,否则a类型变量仅存在于类型签名本身中,而不存在于本地绑定的签名中。

{-# LANGUAGE ScopedTypeVariables #-}

import Data.Tagged

function''' :: forall a. ToString a => Tagged a String
function''' = Tagged $ toString (undefined :: a)

显式foralla实际成为范围变量所必需的,否则扩展名不会启动。

然而....

实际上,最好的方法可能是让class方法首先产生一个标记值:

class NamedType a where
  typeName :: Tagged a String

instance NamedType Double where
  typeName = Tagged "double"
instance NamedType Int where
  typeName = Tagged "int"
...

或者根本不要编写自己的课程,而是使用typeable

import Data.Typeable

typeName' :: Typeable a => Tagged a String
typeName' = fmap show $ unproxy typeRep

当然,这将为您提供实际的大写类型名称,它可能适用于您实际不想要的类型。

答案 1 :(得分:2)

leftaroundabout的答案可能就是你想要的答案。但为了完整起见,还有一件事你可以做:

unwrap :: Wrapper a -> a
unwrap = error "can't actually unwrap"

function'' :: (ToString a) => Wrapper a
function'' = x
  where
    x = Wrapper (toString (unwrap x))

我的想法是,我希望a传递给toString,但只有Wrapper a显示在我的类型中。所以我只定义一个函数,它接受Wrapper a并生成a - 这样的函数不能有真正的实现,但我们还是没有将它用作返回值 - 并将它应用于Wrapper a事。{/ p>

还有一些额外的尴尬,因为Wrapper a显示在结果中而不是参数中,但这种(稍微愚蠢的)递归会解决这个问题。