Home > other >  Pattern synonyms as overloadable 'smart constructors'
Pattern synonyms as overloadable 'smart constructors'

Time:01-10

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 empty pattern I have to introduce an explicit (==) test and supporting Eq constraint -- to achieve a simple pattern match. (I suppose I could call an isEmpty method.)
  • For the singleton pattern, 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.

CodePudding user response:

If you could put pattern synonyms in the class instance, then your solution would be even simpler: you wouldn't need empty or singleton in the class at all as they would be definable in terms of PEmpty and PSingleton. 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.

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.

Alas, this one is unavoidable. Without access to constructors in the class, you need to use explicitly bidirectional patterns.

For the empty pattern I have to introduce an explicit (==) test and supporting Eq constraint -- to achieve a simple pattern match. (I suppose I could call an isEmpty method.)

I personally think your code would be cleaner with an isEmpty method. For what it's worth, you can use a default signature if you like, like:

class Collection c a where
  ...
  isEmpty :: c a -> Bool
  default isEmpty :: Eq (c a) => c a -> Bool
  isEmpty = (==) empty

For the singleton pattern, I've avoided an explicit (==) test, but needed to double-up on a method in the class.

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 extractSingleton method seems quite reasonable, just as headMay :: [a] -> Maybe a (a safe version of head) is quite reasonable.

I really do not like ViewPatterns; I'd hope PatSyns could provide some way to avoid them.

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 COMPLETE pragma for PEmpty and PCons.

  •  Tags:  
  • Related