Choose a typeclass based on return type
I want to be able to have a function, which implementation will choose a typeclass based on the manual type specification of it's return type.
Here's a contrived example: a typeclass and two instances:
class ToString a where
toString :: a -> String
instance ToString Double where
toString = const "double"
instance ToString Int where
toString = const "int"
I'm able to choose the instance by calling the toString with Int
type:
function :: String
function = toString (undefined :: Int)
So far so good. What I'd like to do is to be able to write the function, so it will work for any a
if there exists a typeclass for it:
function' :: (ToString a) => String
function' = toString (undefined :: a)
Here, the function'
doesn't compile, because the a
type parameter is not mentioned anywhere in the signature and it's not possible to specify the typeclass upon calling.
So it looks like the only option is to pass the type information about the type of a
into the return type:
data Wrapper a = Wrapper {
field :: String }
function'' :: (ToString a) => Wrapper a
function'' = Wrapper $ toString (undefined :: a)
showToString :: String
showToString = field (function'' :: Wrapper Int)
I define a Wrapper
type just for carrying the type information. In the showToString
, my hope is that since I specify the exact type of Wrapper
, then the typechecker can infer that the a
in function''
is and Int
and pick the Int
instance of the ToString
typeclass.
But the reality doesn't correspond with my hopes, this is the message from compiler
Could not deduce (ToString a0) arising from a use of `toString'
Is there a way, how to convince the compiler, that he can pick the right typeclass in the function''
, because I specify the it by having the type declaration of :: Wrapper Int
?
First, let me suggest that instead of an own Wrapper
type, you use Data.Tagged.Tagged
, whose purpose is exactly this kind of stuff.
Apart from that, you need to turn on the -XScopedTypeVariables
extension, otherwise the a
type variable only exists in the type signature itself, but not in signatures of local bindings.
{-# LANGUAGE ScopedTypeVariables #-}
import Data.Tagged
function''' :: forall a. ToString a => Tagged a String
function''' = Tagged $ toString (undefined :: a)
The explicit forall
is necessary for a
to actually become a scoped variable, otherwise the extension doesn't kick in.
However....
Actually, the best thing would probably to have the class method produce a tagged value in the first place:
class NamedType a where
typeName :: Tagged a String
instance NamedType Double where
typeName = Tagged "double"
instance NamedType Int where
typeName = Tagged "int"
...
Or don't write your own class at all but use typeable:
import Data.Typeable
typeName' :: Typeable a => Tagged a String
typeName' = fmap show $ unproxy typeRep
Of course, that will give you the actual uppercase type names, and it might work for types you don't actually want it to.
leftaroundabout's answer is probably the one you want. But for completeness, here's one more thing you can do:
unwrap :: Wrapper a -> a
unwrap = error "can't actually unwrap"
function'' :: (ToString a) => Wrapper a
function'' = x
where
x = Wrapper (toString (unwrap x))
The idea is that I want an a
to pass to toString
but only Wrapper a
shows up in my type. So I just define a function which takes Wrapper a
and produces a
– such a function can't have a real implementation, but we're not using it for its return value anyway – and apply it to the Wrapper a
thing.
There's a bit of additional awkwardness because Wrapper a
shows up in the result instead of an argument, but this (slightly silly) recursion takes care of that.
上一篇: 类型结合类型的多态函数
下一篇: 根据返回类型选择一个类型类