There's a fine tradition of including 'smart constructor' methods in an interface (class):
class Collection c a where
empty :: c a
singleton :: a -> c a
-- etc
It would be nice to supply them as pattern-synonyms:
{-# LANGUAGE PatternSynonyms #-}
instance Ord a => Collection [] a where
empty = []
singleton x = [x]
pattern PEmpty = []
pattern PSingleton x = [x]
But you can't put PatSyns inside classes. I've got something working, but it seems an awful lot of ugly code. Can anyone suggest ways to clean it up? (Specific uglinesses itemised below.)
{-# LANGUAGE ViewPatterns #-}
pattern PEmpty :: (Eq (c a), Collection c a) => c a
pattern PEmpty <- ((==) empty -> True) where
PEmpty = empty
pattern PSingleton :: (Ord a, Collection c a) => a -> c a
pattern PSingleton x <- (isSingleton -> Just x) where
PSingleton x = singleton x
-- needs an extra method in the class:
isSingleton :: c a -> Maybe a
-- instance ...
isSingleton [x] = Just x
isSingleton _ = Nothing
- It's annoying that these need to be explicitly bi-directional patterns; especially since the 'under
where' line is so directly comparable to the instance overload. - For the
emptypattern I have to introduce an explicit(==)test and supportingEqconstraint -- to achieve a simple pattern match. (I suppose I could call anisEmptymethod.) - For the
singletonpattern, I've avoided an explicit(==)test, but needed to double-up on a method in the class. - I really do not like
ViewPatterns; I'd hope PatSyns could provide some way to avoid them.
If you could put pattern synonyms in the class instance, then your solution would be even simpler: you wouldn't need
emptyorsingletonin the class at all as they would be definable in terms ofPEmptyandPSingleton. Alas, as you know, this is impossible in the current GHC.As is, you can only define functions and types in your class, and without access to constructors (like
[]and:in your list example), you need two functions to define a pattern synonym: one for extracting data from the type and one for embedding into it.As to your specific points about ugliness, some are unavoidable, but for others, there may be a slightly cleaner approach.
Alas, this one is unavoidable. Without access to constructors in the class, you need to use explicitly bidirectional patterns.
I personally think your code would be cleaner with an
isEmptymethod. For what it's worth, you can use a default signature if you like, like:You say this like you had a choice, but I don't think you can write this pattern with just an equality test. You're trying to extract a value from an unknown collection, and I don't see how you can do that without support from your class. An
extractSingletonmethod seems quite reasonable, just asheadMay :: [a] -> Maybe a(a safe version ofhead) is quite reasonable.This is another "alas" moment. I'm sorry you don't like
ViewPatterns, but they're pretty much the only way to write bidirectional pattern synonyms.The big thing you didn't mention is the issue around complete pattern matches. You may find that even when you have these new patterns, they'll throw all sorts of warnings about non-exhaustive pattern matches. You probably want to define a more general "cons"-like pattern for destructing your collections. Then, you can add a
COMPLETEpragma forPEmptyandPCons.