I think I get it with the "unusual form" of constraints. Just checking ...
The examples in that section for Provided constraints (the second one) all seem to involve GADTs/existentials inside the data constructor(?) With the answer here, I'm not sure if an existential is involved (in the Vinyl library), but the type for rhead
has a more specific type than the argument that f
is passing to it. And tbh the PatternSynonym
seems to be a red herring: the arguments to OnlyRecord
don't appear on f
's rhs.
Required constraints (the first one of two, or the only one if there's one) seem to give the same functionality as the now-deprecated DatatypeContexts
(?) For example
data NoCSet a where -- no constraints on the datatype
NilSet_ :: NoCSet a
ConsSet_ :: a -> NoCSet a -> NoCSet a
deriving (Eq, Show, Read)
pattern ConsSet :: (Eq a) => () => a -> NoCSet a -> NoCSet a
-- Req'd => Prov'd => type; Prov'd is empty, so omittable
pattern ConsSet x xs = ConsSet_ x xs
pattern NilSet :: (Eq a) => () => NoCSet a
pattern NilSet = NilSet_
ccUnit = ConsSet () NilSet -- accepted
-- ccid = ConsSet id NilSet -- rejected no instance Eq (a -> a)
-- ncid = NilSet :: NoCSet (a -> a) -- ditto
with the extra advantage I can put a Required constraint on a pattern NilSet
without an argument, disallowing building even an empty set with an unacceptable type. DatatypeContexts
for constructors like that just ignore the constraint.
Edit/focussed question: (response to @Noughtmare comment)
Is there an observable difference between pattern ConsSet
defined above vs constructor DCConsSet
here:
data (Eq a) => DCSet a = -- constraint on the datatype
DCNilSet
| DCConsSet a (DCSet a)
deriving (Eq, Show, Read)
I mean "difference" other than one's a pattern, one's a constructor. I've tried using them in both building and matching values; I get same behaviour.
No, in general, required constraints don't simply give the same functionality as data type contexts. Data type contexts introduce a constraint that is required to either construct or destruct the value for no reason other than the programmer who created the data type wants to force users of that data type to satisfy that constraint. They are deprecated because it's now considered bad practice to require a constraint for an operation (data type construction or destruction) that does not need the constraint. Instead, the constraints should be moved closer to the "usage site". When the data type is used in a manner that requires a constraint, that's where the constraint should appear. (It sounds like you're familiar with this reasoning.)
In contrast, when used correctly, required constraints on patterns introduce a constraint that is required to construct or destruct the value because the construction or destruction of that value makes use of the provided instance.
For example, given:
the inferred type of
Length
is:Here,
Foldable t
is a required constraint here because it is actually needed when matching the pattern.As you note, you can misuse patterns with required constraints to mimic the deprecated data type contexts extension, but while you're technically not using the deprecated extension, you're still doing something that's widely considered bad practice.
As for your final question, data types with context and patterns with required constraints are handled rather differently by the compiler, so there are bound to be diffferences. One observable difference is that with
DataKinds
enabled,DCNilSet
is a type, whileNilSet
isn't.If your real question is "does the existence of required constraints on patterns represent an admission on the part of the Haskell community that data type contexts are actually a valuable feature that should have never been deprecated?", I think the answer is probably "no".