(Transferring the discussion, because not relevant to that thread.)
Consider a datatype for representing a Set (as a binary tree). We’ll want the elements to be in Ord
for efficient searching.
{-# LANGUAGE DatatypeContexts #-}
data Ord a => OrdTree a = OrdNil | OrdTree (OrdTree a) a (OrdTree a)
Not a GADT because storing the dictionaries in each node is:
- Very wasteful. It’s the same dict all the way down.
- Completely unnecessary: once a node is inserted, there’s no need to introduce ‘local constraints’/the structure already expresses the invariant. [**]
- If some function wants to compare elements, it will bring its own
Ord
dict with it. (If it merely wants to scan the elements in sequence, just recursive descent l-to-r.)
What’s really really annoying about DataTypeContexts
in the H98 standard is if you merely want to export the tree to a list, you still need to bring an Ord
dict.
A lesser annoyance is that because the type of OrdNil
doesn’t mention type param a
, it doesn’t ask for the dict (evidence that Ord a
). So your code can create an empty Tree in one place, then get rejected at some remote place because the empty tree didn’t apply the constraint. (BTW that behaviour I want was spec’d in Haskell vintage 1990, got nixed because implementers were scared of constraints over ungrounded type params.)
These days I can declare the datatype with no constraints. Then use PatternSynonyms
to require the constraint for build only. And not export the underlying constructors. That’s just clutter.
[**] GADTs could/should support applying constraints at build but not storing them. That’s what PatterSynonym
's weird (Ord a) => () => a -> OrdTree a -> OrdTree a
could usefully mean. But the () =>
is taken as union nothing with the build constraints.
So it’s not merely I find GADTs unhelpful (see the earlier thread); they give the wrong behaviour.