Hi people,

in the last couple days this list has once again seen examples of how our class system is not perfect yet. Here are some of the problems we face:

How we usually resolve some of such issues is with newtype. Among the drawbacks are

After some thought I therefore propose a language extension I call "aspects". Keep in mind that this is a very rough draft just to gauge your reaction.

The core change would be the introduction of a keyword "aspect" that would work in a comparable way to the keyword "module". In other words you could say

	aspect Data.Aspect.ChooseNonEmpty where

	  import qualified Data.Set as Set

	  instance Alternative [] where
	      empty = []
	      a <|> b = if null a then b else a

	  instance Alternative Set.Set where …
	      empty = Set.empty
	      a <|> b = if null a then b else a

Changes compared to a normal module would be:

You also wouldn't import an aspect like a normal module, but with a second syntax extension:

	import Data.List under (Data.Aspect.ChooseNonEmpty)
	import qualified Data.Set as Set hiding (Set)
	import qualified Data.Set under (Default, Data.Aspect.ChooseNonEmpty) as CNE (Set)

So you could also import the same structure twice with different aspects under different names:

	import Data.Bool
	import qualified Data.Bool under (Data.Aspect.All) as All (Bool)
	import qualified Data.Bool under (Data.Aspect.Any) as Any (Bool)

Now, because of the first import, you could use the boolean functions normally, and even use the normal Bool type in signatures. And because of the qualified imports if you want to use one of the Monoid instances, all you would have to do is change Bool in the type signature to Any.Bool or All.Bool respectively, like so:

	allEven :: (Integral a) => [a] -> Bool
	allEven = foldMap even -- error: Could not deduce (Monoid Bool)…

	-- old way
	allEven :: (Integral a) => [a] -> Bool
	allEven = getAll . foldMap (All . even)

	-- new way
	allEven :: (Integral a) => [a] -> All.Bool -- qualified name adds the monoidal aspect (and possibly others)
	allEven = foldMap even -- works

In other words, aspects would only be used for instance lookups. That is also why you could state several aspects at once when importing. Conflicts would be solved as usual: All is well until you try to use class functions that create an ambiguity.

I imagine a few special rules to make backwards compatibility easier. Most importantly, you could define default aspects in several ways:

If you don't specify an aspect while importing, it would be imported under the Default aspect. To hide the Default aspect, just don't add it to the aspect list when importing.

Other random thoughts about this:

The biggest drawbacks from this idea that I can see right now are:

So… feel free to bikeshed and more importantly criticize! What am I overlooking? Would you consider such an idea worthwhile? Happy to hear your thoughts.

Cheers,
MarLinn