1. 8
  1.  

  2. 1

    These language extensions are all useful but, for some of them, I question the unqualified “awesome” label.

    For example, OverlappingInstances. There are few occasions on which I’d use that, rather than newtype on the concrete type that I’d want to behave differently. The pedagogical case for using that extension is String = [Char], but most Haskellers agree that that’s not “the right String type” to begin with: Text is the heavy-duty string and the built-in String is mostly a mix of legacy and pedagogy.

    Pattern Synonyms seems powerful but dangerous. Haskell has (as one learns on studying Template Haskell) four fundamental language items: expressions, declarations, types, and patterns. (Pattern-matching, while indispensable, is already a bit much for brand new Haskellers, because patterns look like values/expressions but are something different.) Putting more complexity into patterns seems risky. I’m glad that the extension exists, but I’d be cautious about using it on a large project, since it seems like the risk of DSL hell is present (although perhaps I’m exaggerating the risks or being curmudgeonly).

    Of course, much of this is subjective, and I’d be sad if MultiParamTypeClasses, GADTs, and FunctionalDependencies weren’t there.

    OP: have you thought about taking on type families?

    1. 3

      I can answer part of this.

      One of the pressures for OverlappingInstances is when you want to provide a default behavior that works on some relatively generic type, like instance (Data a) => FavoriteSerializationFormat a. Since the constraint isn’t part of the instance head, which is the information considered for instance resolution, this overlaps with instance FavoriteSerializationFormat SomeSpecificData. OverlappingInstances lets these both be found by instance resolution, then picks the more specific one.

      It’s a bad way to get at that goal, though, because the (Data a) => example is of identical specificity to instance (SlightlyLessGenericData a) => FavoriteSerializationFormat a. You might hope that it could at least resolve them if the definition of SlightlyLessGenericData were class (Data a) => SlightlyLessGenericData a, but it can’t, none of that gets taken into account.

      If you really wanted to do this, you’d have to turn on IncoherentInstances, and then carefully make sure all of your transitive imports provided exactly one or the other of the two instances. But you almost certainly don’t want that; IncoherentInstances is one of those extensions where it’s very hard to avoid using it yourself if any module you import uses it, so it’s a rude thing to do to your downstream programmers.

      There’s actually a much less-bad way, which is to put the (Data a) => context on the class declaration, and give default implementations of all the methods. It is burdensome on your users to have to derive Data on their own types, especially when they don’t intend to make use of your defaults, but a lot less so than the extension-based options.

      A lot of the design pressure exists because of the way instance visibility and resolution are entirely implicit behaviors with no manual control. But it’s not obvious how to change the language to avoid that.