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.