[Haskell-cafe] data type declaration

John Lato jwlato at gmail.com
Sun Jul 25 20:35:47 EDT 2010


> From: Patrick Browne <patrick.browne at dit.ie>
>
> 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


More information about the Haskell-Cafe mailing list