根据返回类型选择一个类型



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

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

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类型参数不会在签名中的任何地方提及,也无法在调用时指定Typeclass。

因此,看起来唯一的选择是将有关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的确切类型,因此Typechecker可以推断function''中的a IS和Int中的CC_9并选择ToString Typeclass的Int实例。

但是现实与我的希望不符,这是编译器的消息

无法通过使用" tostring"

引起的(tostring a0)

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

首先,我建议您使用Data.Tagged.Tagged,而不是自己的Wrapper类型,其目的正是这种东西。

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

{-# LANGUAGE ScopedTypeVariables #-}
import Data.Tagged
function''' :: forall a. ToString a => Tagged a String
function''' = Tagged $ toString (undefined :: a)

a实际成为范围的变量是必需的,否则范围不会启动。

但是....

实际上,最好的事情可能会使类方法首先产生标记值:

class NamedType a where
  typeName :: Tagged a String
instance NamedType Double where
  typeName = Tagged "double"
instance NamedType Int where
  typeName = Tagged "int"
...

或根本不编写自己的课程,但只使用可拼写的课程:

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

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

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 Thing。

有一些额外的尴尬性,因为 Wrapper a result中出现而不是参数,但是(有点愚蠢的)递归会照顾好。

现在值得一提的是,现有的答案到目前为止已经过时了,因为GHC现在已经有了更好的机制来实现此目的。基本上,原始

function' :: (ToString a) => String

现在,如果您打开-XAllowAmbiguousTypes,则现在是。您仍然需要范围的类型变量来实现它,因此:

{-# LANGUAGE AllowAmbiguousTypes, ScopedTypeVariables, UnicodeSyntax #-}
function' :: ∀ a . ToString a => String
function' = toString (undefined :: a)

正如我之前所建议的那样,您只需立即在班级中使用它,这将变得更加容易:

{-# LANGUAGE AllowAmbiguousTypes #-}
class NamedType a where
  typeName :: String
instance NamedType Double where
  typeName = "double"
instance NamedType Int where
  typeName = "int"

带有两个版本的警告是您需要特殊的语法来选择给定用例中的实例。但这很容易:

ghci> :set -XTypeApplications
ghci> typeName @Double
"double"
ghci> typeName @Int
"int"

最新更新