
From: Patrick Browne
Andrew, Thanks for your detailed feedback, it is a great help. I appreciate that the code does not do anything useful, nor is it an appropriate way to write Haskell, but it does help me understand language constructs. I have seen statements like
data C3 c3 a => Address c3 a = Address c3 a
Incidentally, there seems to be a consensus that this a Bad Idea [1]. Even when you specify a type class context on a data declaration, Haskell still requires you to specify the context on functions that use that data (Address c a). What's worse is that you need the class restriction for *all* functions that use an Address, even if they don't operate on the component parts and don't make use of the type class at all. Basically, it ends up making all your type signatures longer with no benefit. If you really want to ensure that every "Address c a" has this context, then use a smart constructor: address :: C3 c3 a => c3 -> a -> Address c3 a address = Address and don't export the Address data constructor. Also, Andrew said that type classes are closer to Java interfaces than C++ objects. This is true, but you may find the difference useful too. A small example of a common OOP-er mistake should demonstrate: -- return a Float if True, else an Int. At least it would if type classes were interfaces. toFloat :: Num a => Int -> Bool -> a toFloat x True = (fromIntegral x) :: Float toFloat x False = x OOP-style interfaces have two features: they specify methods that are available for all types which implement the interface, and they are an existentially-quantified data constructor. Equivalent code in Java would return an existentially-quantified type - we know the interface methods are supported but not the data type, so the interface methods are all we can do. The exact type (either Float or Int) is wrapped in this existentially quantified box. Haskell type classes don't do existential quantification, which is why the above doesn't compile. The return value of this function must be any a with a Num instance, and the caller (not toFloat) gets to choose the exact type to instantiate. Translating an interface to Haskell requires that you write both parts: class Num a where ... data INum = forall a. Num a => INum a --requires the ExistentialQuantification extension -- now toFloat works toFloat :: Int -> Bool -> INum toFloat x True = INum ((fromIntegral x) :: Float) toFloat x False = INum x Now the type variable is stuck in the INum type instead of being universally quantified (top-level forall), which is what an interface does. I think most Haskellers would prefer to choose a design that doesn't require existential quantification, but it really depends on the problem. John [1] GHC sources refer to this context as "stupid theta", see further discussion at http://hackage.haskell.org/trac/haskell-prime/wiki/NoDatatypeContexts