Two language extensions

Below are my ideas for two languages extensions which I think add a lot to the Haskell language without adding too much ambiguity. At least, I haven’t found any issues with the ideas so far, but I’m sure plenty of other people will be able to ;).

AutomaticClassSynonyms

Let’s say I’ve got the Failure class, and I’d like to define a MonadFailure class- simply for convenience- which is a subclass of both Failure and Monad. Well, defining the class is easy:

class (Monad m, Failure m) => MonadFailure m

I believe that this should automatically make anything which is an instance of both Monad and Failure an instance of MonadFailure, since the definition of MonadFailure is completely empty. I look at class instances as needing to address two issues:

  • Existence: is there some instance which makes sense?
  • Uniqueness: of those instances which make sense, which one should I use?

Here, there is no room for ambiguity: there exists an instance which makes sense (eg, instance MonadFailure Maybe), and there is precisely one instance which makes sense. There is no alternative way to define this instance.

Therefore, I think that in this case we should not complain if we have two instances for the same data type, since we know that the instances will be identical. That would make this extension work very nicely with existing code. It also adds no new syntax.

What I didn’t say

I specifically do not think this extension should make automatic instances of classes which have default definitions for all its functions. The first example that comes to mind is Exception: even though both fromException and toException have default definitions, I think the user should still have to explicitly instanciate exception, even if a type is already an instance of Typeable and Show.

SubClassOverloading

This extension is a bit more complicated. For motivation, let’s look at the interaction between Monad and Applicative. For most cases, a Monad can define an Applicative instance as such:

instance Functor MyMonad where
  fmap = liftM
instance Applicative MyMonad where
  pure = return
  (<*>) = ap

Well, that’s irritating! Instead of just writing a five line Monad instance, I have to write five extra boilerplate lines.

As a separate issue, Applicative is not defined as a superclass of Monad, and therefore I cannot treat all Monads as Applicatives. But we can’t add that superclass requirement without breaking existing code.

So I say we allow the definition of Monad as such:

class Applicative m => Monad m where
  fail s :: s -> m a -- or we could just take this out...
  (>>=) :: m a -> (a -> m b) -> m b -- the same
  return :: a -> m a -- also the same
  fmap = liftM -- a default definition for a superclass function
  pure = return
  (<*>) = ap

And suddenly all Monads are Applicative! Since every function in the Functor and Applicative classes is a given default definition in Monad, they can be automatically derived.

But what if you want to define a special version of fmap? Simple: do it like always! The definition in Monad is merely the default; if the compiler finds a separate instance for your data type, it uses that instead. This way, old code still works without a hitch.

The downside

The only downside I can see is that suddenly you’ll have instances of classes where before there were none. Not that having Applicative instances in and of itself is a downside, but there might be cases where it would define inappropriate instances (not that I can think of any off-hand). On the other hand, this would be mitigated slightly by the requirement of the type-class author to explicitly turn on this flag.

Let the beatings begin

Well, this is my first time suggesting any changes to Haskell, so I expect to be thoroughly scolded for my perposterous, heratical notions. Even if these suggestions are lacking, however, I hope we eventually get something which allows these kinds of features in Haskell.

About these ads

8 Responses to “Two language extensions”

  1. lilac Says:

    AutomaticClassSynonyms:

    This extension buys very little. In addition to

    class (Monad m, Failure m) => MonadFailure m

    you can just write (with the appropriate language extensions)

    instance (Monad m, Failure m) => MonadFailure m

    … and you’re done.

    SubClassOverloading:

    Fully-automatic instance generation is almost certainly a mistake. There are plenty of types which have a Monad instance defined in the same module, and an Applicative instance defined elsewhere; you’d break all of these, automatically, in a way the compiler maybe won’t even detect. It’d also mean you can’t read the code and figure out which instances it defines (without reference to the source code of the classes it’s defining instances of).

    There’s a SuperClassDefaults proposal which resolves this issue in a not-too-verbose manner; see here:
    http://www.haskell.org/haskellwiki/Superclass_defaults
    That proposal, however, only allows defaults for superclasses, which means that Monad can’t trivially define Applicative defaults. One can resolve that with a ‘MonadAndApplicative’ class which defines extra superclass defaults, though…

    • Michael Snoyman Says:

      @lilac

      AutomaticClassSynonyms:
      You’re right that you can do this with the appropriate language extensions. I think this extension would be less invasive than those other language extensions, and I don’t see it costing us anything.

      SubClassOverloading:
      I don’t see why having the Applicative instance declared elsewhere would be a problem. I would imagine it working very similarly took overlapping instances, so that once you import the other modules, their definition of Applicative will take over. You might complain that this will change the definition of a function based on which modules are imported; I’d say this is what you getting for having orphan instances.

      SuperClassDefaults might be nice, but I think my proposal is easier to follow, and introduces no new syntax. Also, keep in mind when doing comparisons: I only gave the example of Applicative as one use case; there are many other use cases that do not involve the issues you are alluding to.

  2. Dylan Thurston Says:

    I frequently use idioms like this in a way your proposal would break. In particular, the following definitions are useful:

    class Monoid m where
    (**) : m -> m -> m

    class (Monoid m) => CommutativeMonoid m

    The CommutativeMonoid instance should only be declared if the Monoid operation is actually commutative, and then functions that assume that the monoid operation is commutative can declare that.

    • Michael Snoyman Says:

      That’s a good counter example, thank you. I’d have two options for you:

      1. Don’t enable the language extension for the file that defines CommutativeMonoid
      2. Define a dummy function with a default definition

      Not sure if that would be acceptable to you.

  3. Jeff Says:

    There is “class alias” proposal at http://repetae.net/recent/out/classalias.html which suggests something very similar, but in a way that avoids all the incompatibilities and difficulties.

    • Michael Snoyman Says:

      @Jeff,

      Looks like a really good proposal. You have any information on its status? As far as I can tell, it actually *does* address all the issues I’m having (plus quite a few more). Looks like the only downside would be the alias keyword and ugly interactions with other extensions- not that I can think of any.

      Thanks for the link!

  4. Rob Says:

    Have you looked at this pretty recent paper? It looks systematically at the problem managing constraints on types, and proposes more general solutions (I think) than the class alias proposal.

    http://www.cs.kuleuven.be/~toms/Research/papers/constraint_families.pdf

  5. Remi Turk Says:

    Then there is also http://haskell.org/haskellwiki/Comparing_class_alias_proposals
    which aims to collect and compare the different class alias variations.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: