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 type Frob
, since it is being given to getFrobbable
.
(getFrobbable someFrob)
must be of type Frob2
since it is being given to useFrobbable
- But by the equation
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.