Writing a function with a universally-quantified return type
If I write
foo :: (Num a) => a
foo = 42
GHC happily accepts it, but if I write
bar :: (Num a) => a
bar = (42 :: Int)
it tells me that the expected type a
doesn't match the inferred type Int
. I don't quite understand why, since Int
is an ins开发者_运维知识库tance of the class Num
that a
stands for.
I'm running into this same situation while trying to write a function that, boiled down to the core of the problem, looks roughly like this:
-- Note, Frob is an instance of class Frobbable
getFrobbable :: (Frobbable a) => Frob -> a
getFrobbable x = x
Is it possible to write a function like this? How can I make the result compatible with the type signature?
You are treating typeclass constraints as if they were subtype constraints. This is a common thing to do, but in fact they only coincide in the contravariant case, that is, when concerning function arguments rather than results. The signature:
bar :: (Num a) => a
Means that the caller gets to choose the type a
, provided that it is an instance of Num
. So here, the caller may choose to call bar :: Int
or bar :: Double
, they should all work. So bar :: (Num a) => a
must be able to construct any kind of number, bar
does not know what specific type was chosen.
The contravariant case is exactly the same, it just corresponds with OO programmers' intuition in this case. Eg.
baz :: (Num a) => a -> Bool
Means that the caller gets to choose the type a
, again, and again baz
does not know what specific type was chosen.
If you need to have the callee choose the result type, just change the signature of the function to reflect this knowledge. Eg.:
bar :: Int
Or in your getFrobbable
case, getFrobbable :: Frob -> Frob
(which makes the function trivial). Anywhere that a (Frobbable a)
constraint occurs, a Frob
will satisfy it, so just say Frob
instead.
This may seem awkward, but it's really just that information hiding happens at different boundaries in functional programming. There is a way to hide the specific choice, but it is uncommon, and I consider it a mistake in most cases where it is used.
One way to get the effect your sample seems to be going for
I first want to describe a way to accomplish what it appears you're going for. Let's look at your last code sample again:
-- Note, Frob is an instance of class Frobbable
getFrobbable :: (Frobbable a) => Frob -> a
getFrobbable x = x
This is essentially a casting operation. It takes a Frob
and simply forgets what it is, retaining only the knowledge that you've got an instance of Frobbable
.
There is an idiom in Haskell for accomplishing this. It requires the ExistentialQuantification
extension in GHC. Here is some sample code:
{-# LANGUAGE ExistentialQuantification #-}
module Foo where
class Frobbable a where
getInt :: a -> Int
data Frob = Frob Int
instance Frobbable Frob where
getInt (Frob i) = i
data FrobbableWrapper = forall a . Frobbable a => FW a
instance Frobbable FrobbableWrapper where
getInt (FW i) = getInt i
The key part is the FrobbableWrapper
data structure. With it, you can write the following version of your getFrobbable
casting function:
getFrobbable :: Frobbable a => a -> FrobbableWrapper
getFrobbable x = FW x
This idiom is useful if you want to have a heterogeneous list whose elements share a common typeclass, even though they may not share a common type. For instance, while Frobbable a => [a]
wouldn't allow you to mix different instances of Frobbable
, the list [FrobbableWrapper]
certainly would.
Why the code you posted isn't allowed
Now, why can't you write your casting operation as-is? It's all about what could be accomplished if your original getFrobbable
function was permitted to type check.
The equation getFrobbable x = x
really should be thought of as an equation. x
isn't being modified in any way; thus, neither is its type. This is done for the following reason:
Let's compare getFrobbable
to another object. Consider
anonymousFrobbable :: Frobbable a => a
anonymousFrobbable = undefined
(Code involving undefined
are a great source of awkward behavior when you want to really push on your intuition.)
Now suppose someone comes along and introduces a data definition and a function like
data Frob2 = Frob2 Int Int
instance Frobbable Frob2 where
getInt (Frob2 x y) = y
useFrobbable :: Frob2 -> [Int]
useFrobbable fb2 = []
If we jump into ghci we can do the following:
*Foo> useFrobbable anonymousFrobbable
[]
No problems: the signature of anonymousFrobbable
means "You pick an instance of Frobbable, and I'll pretend I'm of that type."
Now if we tried to use your version of getFrobbable
, a call like
useFrobbable (getFrobbable someFrob)
would lead to the following:
someFrob
must be of typeFrob
, since it is being given togetFrobbable
.(getFrobbable someFrob)
must be of typeFrob2
since it is being given touseFrobbable
- But by the equation
getFrobbable someFrob = someFrob
, we know thatgetFrobbable someFrob
andsomeFrob
have the same type.
Thus the system concludes that Frob
and Frob2
are the same type, even though they are not. Hence this reasoning is unsound, which is ultimately the type of rational behind why the version of getFrobbable
you posted doesn't type-check.
Also worth noting is that the literal 42
actually stands for fromInteger (42 :: Integer)
, which really has type (Num a) => a
. See the Haskell Report on numeric literals.
A similar mechanism works for floating point numbers and you can get GHC to use overloaded string literals, but for other types there is (to my knowledge) no way to do that.
I am a teensy bit confused as to what you're trying to do, but if you just want the caller's use of the return value to drive instance selection, then that's normal typeclasses at work.
data Frob = Frob Int Float
class FrobGettable a where
getFrobbable :: Frob -> a
instance FrobGettable Int where
getFrobbable (Frob x _) = x
instance FrobGettable Float where
getFrobbable (Frob _ y) = y
instance FrobGettable Char where
getFrobbable _ = '!'
frob = Frob 10 3.14
test1 :: Float
test1 = getFrobbable frob * 1.1
test2 :: Int
test2 = getFrobbable frob `div` 4
test3 = "Hi" ++ [getFrobbable frob]
We can use GHCi to see what we have,
*Main> :t getFrobbable
getFrobbable :: (FrobGettable a) => Frob -> a
精彩评论