Reputation: 34563
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 instance 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?
Upvotes: 2
Views: 353
Reputation: 3791
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
Upvotes: 0
Reputation: 6610
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.
Upvotes: 2
Reputation: 1011
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.
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 type Frob
, since it is being given to getFrobbable
.(getFrobbable someFrob)
must be of type Frob2
since it is being given to useFrobbable
getFrobbable someFrob = someFrob
, we know that getFrobbable someFrob
and someFrob
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.
Upvotes: 3
Reputation: 60463
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.
Upvotes: 11