Design for DatatypeContexts: what was the idea, anyway?

Previous Topic Next Topic
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view

Design for DatatypeContexts: what was the idea, anyway?

Quite prosaically: the design for DatatypeContexts seems to have materialised with no visible discussion, neither agreement nor disagreement; and persisted almost unchanged until they got deprecated nearly 20 years later.

I can guess there was a verbal discussion amongst the Committee circa 1990/91; that somebody was deputed to write down the conclusion (memo March 1991 [1]) ; and the implementers (both Committee members) went to work in Hugs and GHC. Much later (1999, while preparing the Haskell 1998 Report, published 2002) a fresh set of eyes spotted an under-explicitness that had been interpreted differently by the implementers; and there was a Committee thread resolving it. That thread is the best place from which to understand the intent, particularly Phil Wadler's comments [2].

Historical context: typeclasses/constraints/contexts were the new shiny thing, still rapidly evolving (Wadler early 1989, Wadler & Blott late 1989 but with several open questions). They were initially tied to overloading methods; by 1990 the Haskell report v1.0 included constraints for Numeric Literals, but with several caveats. The mailing lists had much back-and-forth about ambiguities and defaulting and (of course) the Dreaded Monomorphism Restriction for constrained terms. I see comments about the "dictionary transfer problem" and dictionaries "shared lazily between all applications to each elt [in a data structure]".

The syntax of prefixing the context to the type constructor name (syntax in the 1990 Haskell Report, along with the admittedly vague descriptions there), suggests to me they were thought of as for numeric literals: `5 :: Num a => a` so `(ConsSet 5 NilSet) :: Num a => Set a`, where Set's `Eq a` is implied, as a superclass of Num. There's no visible method invocation. If the intent had been that the context attached only inside the data constructors, I'd expect syntax like one of these:

    data Set a = NilSet | (Eq a) => ConsSet a (Set a)
    data Set a = NilSet | ConsSet (Eq a) => a (Set a)

(Syntax closer to that second did arrive later with GADTs.)

The 1999 discussion concluded that the context should be exposed outside of a pattern match, even though a match only 'consumes' an already constructed value. Exposing makes sense if all data constructors enforce the context, but from the 1991 design, `NilSet`'s type doesn't mention the context (because there's no argument to the constructor to ground the type?). I'm confused by that: if Haskell 1990 supported `5 :: Num a => a`, what's wrong with `NilSet :: Eq a => Set a`?

I sense quite a bit of exasperation in that 1991 memo: "Nobody has been able to give a satisfactory account", "Nobody has been able to explain", several phrases in block caps. The so-called "simplifications" are almost what Parliamentarians would call a 'wrecking amendment' (undermining or nullifying the intent of a proposal) or even applying Cunningham's Law (the quickest way to get the right answer is not to ask a question, but to post a wrong answer) -- except nobody (visibly) pushed back.

And there's hints of dissent unresolved up to the 1999 exchange: "a broken feature of H98", "What you propose, I think, offers the worst of both worlds",   "a compile-time language feature can be hard to understand if it is not directly tied to run-time behaviour.",  "Live and learn.". (Who should learn? Is that a self-admonition or aimed at others?)

It's well known that SPJ dubbed the feature "stupid theta". The thing is: the scribe in 1991 was SPJ; even if he was only writing from dictation, why specify and then implement a "broken feature"? Or did it only earn the "stupid" as a result of the 1999 decision? Then it's notable in that decision that Hugs' behaviour was preferred; GHC was changed to align; but then GADTs design reverted to the GHC pre-1999 behaviour (which is reasonable considering different constructors have different constraints and return types).

Pattern Synonyms support double-contexts in the signature, to be explicit about what's exposed/required outside a pattern match.

I see much recent traffic (say on StackOverflow) that GADTs are DatatypeContexts 'done right'. I can't agree with that: GADTs are addressing a different set of use cases vs. the motivation Wadler explains in 1999 -- to enforce invariants -- which makes sense to me.


(The question is posed in the preceding message in the thread; follow the thread through to see the number of luminaries disagreeing with GHC's then behaviour.)

Haskell-Cafe mailing list
To (un)subscribe, modify options or view archives go to:
Only members subscribed via the mailman list are allowed to post.