我希望能够拥有一个函数,哪个实现将根据其返回类型的手动类型规范选择类型。
这是一个人为的示例:一个类型和两个实例:
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"