根据 return 类型选择类型类别
Choose a typeclass based on return type
我希望能够有一个函数,它的实现将根据它的 return 类型的手动类型规范选择一个类型类。
这是一个人为的例子:一个类型类和两个实例:
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
类型的类型信息传递给 return 类型:
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
的确切类型,所以类型检查器可以推断 function''
中的 a
是 Int
并选择 ToString
类型类的 Int
实例。
但现实与我的希望不符,这是编译器的消息
Could not deduce (ToString a0) arising from a use of `toString'
有没有办法让编译器相信他可以在 function''
中选择正确的类型类,因为我通过 :: Wrapper Int
的类型声明来指定它?
首先,我建议您不要使用自己的 Wrapper
类型,而是使用 Data.Tagged.Tagged
,其目的正是这种东西。
除此之外,你需要开启-XScopedTypeVariables
扩展,否则a
类型变量只存在于类型签名本身,而不存在于本地绑定的签名中。
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Tagged
function''' :: forall a. ToString a => Tagged a String
function''' = Tagged $ toString (undefined :: a)
显式 forall
是 a
实际成为作用域变量所必需的,否则扩展不会启动。
然而……
实际上,最好的办法可能是让 class 方法首先产生一个标记值:
class NamedType a where
typeName :: Tagged a String
instance NamedType Double where
typeName = Tagged "double"
instance NamedType Int where
typeName = Tagged "int"
...
或者根本不写自己的class,而是使用typeable:
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
的函数——这样的函数不能有一个真正的实现,但无论如何我们都不会使用它作为它的 return 值——并且将其应用于 Wrapper a
事物。
还有一些额外的尴尬,因为 Wrapper a
出现在 结果中 而不是参数,但是这个(有点傻)递归解决了这个问题。
我希望能够有一个函数,它的实现将根据它的 return 类型的手动类型规范选择一个类型类。
这是一个人为的例子:一个类型类和两个实例:
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
类型的类型信息传递给 return 类型:
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
的确切类型,所以类型检查器可以推断 function''
中的 a
是 Int
并选择 ToString
类型类的 Int
实例。
但现实与我的希望不符,这是编译器的消息
Could not deduce (ToString a0) arising from a use of `toString'
有没有办法让编译器相信他可以在 function''
中选择正确的类型类,因为我通过 :: Wrapper Int
的类型声明来指定它?
首先,我建议您不要使用自己的 Wrapper
类型,而是使用 Data.Tagged.Tagged
,其目的正是这种东西。
除此之外,你需要开启-XScopedTypeVariables
扩展,否则a
类型变量只存在于类型签名本身,而不存在于本地绑定的签名中。
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Tagged
function''' :: forall a. ToString a => Tagged a String
function''' = Tagged $ toString (undefined :: a)
显式 forall
是 a
实际成为作用域变量所必需的,否则扩展不会启动。
然而……
实际上,最好的办法可能是让 class 方法首先产生一个标记值:
class NamedType a where
typeName :: Tagged a String
instance NamedType Double where
typeName = Tagged "double"
instance NamedType Int where
typeName = Tagged "int"
...
或者根本不写自己的class,而是使用typeable:
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
的函数——这样的函数不能有一个真正的实现,但无论如何我们都不会使用它作为它的 return 值——并且将其应用于 Wrapper a
事物。
还有一些额外的尴尬,因为 Wrapper a
出现在 结果中 而不是参数,但是这个(有点傻)递归解决了这个问题。