Opened 3 years ago

Last modified 9 months ago

## #12001 new feature request

# RFC: Add pattern synonyms to base

Reported by: | Iceland_jack | Owned by: | |
---|---|---|---|

Priority: | normal | Milestone: | |

Component: | libraries/base | Version: | 7.10.3 |

Keywords: | Cc: | ||

Operating System: | Unknown/Multiple | Architecture: | Unknown/Multiple |

Type of failure: | None/Unknown | Test Case: | |

Blocked By: | Blocking: | ||

Related Tickets: | Differential Rev(s): | ||

Wiki Page: |

### Description (last modified by )

Since we have pattern synonyms it's worth considering if some belong in base:

Data.Complex.Lens contains patterns that could be defined in base, here are some more suggestions:

### Data.Array

pattern ArrayIx :: Ix i => (i, i) -> [(i, e)] -> Array i e pattern ArrayIx low'high xs <- ((\arr -> (bounds arr, assocs arr)) -> (low'high, xs)) where ArrayIx low'high xs = array low'high xs

### Data.Bits

pattern ZeroBits :: (Eq a, Bits a) => a pattern ZeroBits <- ((== zeroBits) -> True) where ZeroBits = zeroBits pattern BitSize :: Bits a => Int -> a pattern BitSize n <- (bitSizeMaybe -> Just n) pattern Signed :: Bits a => a pattern Signed <- (isSigned -> True) pattern Unsigned :: Bits a => a pattern Unsigned <- (isSigned -> False) pattern PopCount :: Bits a => Int -> a pattern PopCount n <- (popCount -> n)

### Data.Char

pattern ControlChar :: Char pattern ControlChar <- (isControl -> True) pattern SpaceChar :: Char pattern SpaceChar <- (isSpace -> True)

### Data.Complex

import Data.Complex (Complex, conjugate, polar, mkPolar) import qualified Data.Complex as C pattern Conjugate :: Num a => Complex a -> Complex a pattern Conjugate a <- (conjugate -> a) where Conjugate a = conjugate a pattern Polar :: RealFloat a => a -> a -> Complex a pattern Polar m theta <- (polar -> (m, theta)) where Polar m theta = mkPolar m theta -- See https://github.com/ekmett/lens/issues/653 pattern Real :: Num a => a -> Complex a pattern Real r <- r C.:+ _ where Real r = r C.:+ 0 pattern Imaginary :: Num a => a -> Complex a pattern Imaginary i <- _ C.:+ i where Imaginary i = 0 C.:+ i pattern (:+) :: a -> a -> Complex a pattern (:+) { realPart, imagPart } = realPart C.:+ imagPart

### GHC.Float

pattern NegativeZero :: RealFloat a => a pattern NegativeZero <- (isNegativeZero -> True) where NegativeZero = -0 pattern Denormalized :: RealFloat a => a pattern Denormalized <- (isDenormalized -> True) pattern NaN :: RealFloat a => a pattern NaN <- (isNaN -> True) where NaN = 0 / 0 -- How ever negative infinity is handled pattern Infinity :: RealFloat a => a pattern Infinity <- (isInfinite -> True) where Infinity = 1 / 0

### Foreign.Ptr

pattern NullPtr :: Ptr a pattern NullPtr <- ((==) nullPtr -> True) where NullPtr = nullPtr

Used here

### Change History (16)

### comment:1 Changed 3 years ago by

### comment:2 Changed 3 years ago by

Some random ideas,

### Data.Functor.Const

pattern K :: forall a (b :: k). a -> Const a b pattern K a = Const a

### Data.Functor.Constant

pattern K' :: forall a (b :: k). a -> Constant a b pattern K' a = Constant a

### GHC.Generics

isAssociative :: Associativity -> Bool isAssociative LeftAssociative = True isAssociative RightAssociative = True isAssociative NotAssociative = False pattern Associative :: Associative pattern Associative <- (isAssociative -> True)

### Data.Ratio

pattern Denominator :: a -> Ratio a pattern Denominator a <- (denominator -> a) pattern Numerator :: a -> Ratio a pattern Numerator a <- (numerator -> a) pattern (:%) :: Integral a => a -> a -> Ratio a pattern num :% den <- ((\r -> (numerator r, denominator r)) -> (num, den)) where num :% den = num % den

### System.Exit

exitCode :: ExitCode -> Int exitCode = \case ExitSuccess -> 0 ExitFailure n -> n pattern ExitCode :: Int -> ExitCode pattern ExitCode n <- (exitCode -> n) where ExitCode 0 = ExitSuccess ExitCode n = ExitFailure n

### Data.Maybe

pattern Some :: a -> Maybe a pattern Some a = Just a pattern None :: Maybe a pattern None = Nothing

### Data.Functor.Identity

pattern I :: a -> Identity pattern I a = Identity a

### Data.Ord

pattern Less :: Ordering pattern Less = LT pattern Equal :: Ordering pattern Equal = EQ pattern Greater :: Ordering pattern Greater = GT

### Data.Foldable

pattern Null :: Foldable f => f a pattern Null <- (null -> True)

### Data.Bool

pattern T :: Bool pattern T = True pattern F :: Bool pattern F = False

It's fine if these don't get accepted, just throwing them into the universe

### comment:3 Changed 3 years ago by

I am very sceptical. Pattern synonyms are still relatively new, and I would say that best practices around them have not evolved yet (e.g.: should they be used for non-injective convenience patterns like `Popcount`

at all? Should they be used for anything else but compatible or alternative views on data types?)

Also, they clutter the namespace.

Therefore, I would prefer if less central libraries would be first to take up pattern synonym and explore usage patterns, until it becomes apparent with which intensity pattern synonym make most sense.

More concretely, of your list above, I’d give a +1 only to `Polar`

. This is a genuine, self-descriptive constructor-like view on a data type. A bit fishy around 0, but that’s inherent in the general concept and not an issue with the constructor.

### comment:4 Changed 3 years ago by

I'm pretty much in the same camp as Joachim: The only one of these that I think really passes muster as a pattern that models a constructor is `Polar`

.

It doesn't destroy information when you pattern match with it and then reconstruct. (It does, however, destroy the phase information if the magnitude is 0 if you construct then deconstruct).

The rest seem all better managed as view patterns, using existing combinators so that their lossy nature is much more clear.

It is worthy of discussion to explore whether we're ready to start incorporating patterns into the bulk of `base`

, but I personally think I'd like to see them endure a couple of releases without the sorts of major overhauls they have going on with how to put signatures on them, etc. before they started taking a more prominent role in a place where they'd be as hard to dislodge as `base`

.

### comment:5 Changed 3 years ago by

Great responses, there are obviously no best practices yet for this extension.

As the dust settles, will we only accept *constructor-like* patterns?
Are non-constructor-like patterns wholly undesirable or are there examples where they are worth it?
I personally use all of these to good effect, let's wait until we understand them better.

Let's identify and categorise some of these examples:

### Literal synonyms

Bidirectional patterns, nothing fancy

pattern I a = Identity a pattern K a = Const a pattern Less = LT

### ‘Getter’ patterns

Unidirectional patterns, these are essentially ‘Getter’s

pattern Real r <- r :+ _ pattern Imaginary i <- _ :+ i

### Constructor-Like Patterns

(Implicitly) bidirectional patterns that don't lose information

pattern Real, Imaginary :: (Num a, Eq a) => a -> Complex pattern Real r = r :+ 0 pattern Imaginary i = 0 :+ i

In this case this is the identity function on complex numbers:

f :: (Num a, Eq a) => Complex a -> Complex a f (Real r) = Real r f complex = complex

@ekmett argues for this property here. It will fail to match any complex number with a non-zero imaginary part: The way I see it, this presents a *bona fide* design decision that I would like to see discussed.

### Non-Constructor-Like Patterns

Explicitly bidirectional patterns that do lose information

pattern Real, Imaginary :: Num a => a -> Complex a pattern Real r <- r :+ _ where Real r = r :+ 0 pattern Imaginary i <- _ :+ i where Imaginary i = 0 :+ i

We lose the constructor property, but matching never fails so it's closer to the lens `_realPart`

.

We can keep this ticket open until the extension and understanding matures, same as #11349 where Data.Proxyless was created in response. It has been said of me that I embrace pattern synonyms to “almost an absurd level” so someone needs to show restraint `:--)`

tickets such as #11977 show that this extension still isn't even fully understood and I look forward to seeing how it evolves

### comment:6 Changed 3 years ago by

Since `Polar`

was the pick of the litter, it's worth mentioning that with record syntax

pattern Polar :: RealFloat a => a -> a -> Complex a pattern Polar {m, theta} <- (polar -> (m, theta)) where Polar m theta = mkPolar m theta

we can write

ghci> (3 :+ 1) { m = 2 } 1.8973665961010275 :+ 0.6324555320336759 ghci> ghci> set (_polar._1) 2 (3 :+ 1) 1.8973665961010275 :+ 0.6324555320336759

### comment:7 Changed 3 years ago by

Jotting down ideas.

### GHC.Generics

pattern From :: Generic a => (Rep a) x -> a pattern From rep <- (from -> rep) where From rep = to rep pattern From1 :: Generic1 f => (Rep1 f) a -> f a pattern From1 rep <- (from1 -> rep) where From1 rep = to1 rep

or corresponding `To`

, `To1`

patterns.

### Text.Read

pattern Read :: Read a => a -> String pattern Read a <- (readMaybe -> Just a)

with the caveat that they parse different types,

foo :: String -> String foo (Read 42) = "answer" foo (Read n) = "some other number " ++ show n foo _ = "can't parse"

ghci> foo "()" "some other number ()"

If the types are made explicit with #11350

foo :: String -> String foo (Read @Integer 42) = "answer" foo (Read @() n) = "some other number " ++ show n foo _ = "can't parse"

it is a common action and pattern (search for `pattern`

and `readMaybe -> Just`

), and is often use to pattern match on numbers. Artificially restricting the type avoids it matching `()`

pattern ReadNumber :: (Num a, Read a) => a -> String

This is cool

ghci> [ n | ReadNumber n <- words "from 2010 ... 2016 they were 4" ] [2010,2016,4]

### comment:8 Changed 3 years ago by

Not sure if makes sense

### Control.DeepSeq

https://www.reddit.com/r/haskell_jp/comments/74f2tr/pepeiborrastricttypes_type_and_value_level/

pattern Force :: NFData a => a -> a pattern Force a <- !(force -> a)

### Debug.Trace

pattern Dbg :: a pattern Dbg <- ((\_ -> trace "DEBUG" False) -> True) -- ? pattern DbgShow :: Show a => a pattern DbgShow <- ((\a -> trace ("DEBUG: " ++ show a) False) -> True) pattern YesDbgShow :: Show a => a -> a pattern YesDbgShow a <- ((\a -> trace ("DEBUG: " ++ show a) a) -> a) where YesDbgShow (!a) = trace ("DEBUG 2: " ++ show a) a `seq` a

-- >>> foob "abc" -- "DEBUG -- abcabc" foob :: String -> String foob Dbg = ... foob a = a ++ a

teaser:

pattern Dbg :: forall a sym. KnownSymbol sym => a pattern Dbg <- ((\_ -> trace (symbolVal (Proxy :: Proxy sym)) False) -> True)

### comment:9 Changed 3 years ago by

With `UnboxedSums`

pattern Left# :: a -> (# a | b #) pattern Left# a = (# a | #) pattern Right# :: b -> (# a | b #) pattern Right# b = (# | b #)

functions that return `Int#`

for True/False (it won't accept a pattern signature with the unlifted type)

pattern False# :: Int# pattern False# = 0# pattern True# :: Int# pattern True# = 1#

TODO When we get newtypes over unlifted types add

newtype Bool# = Bool# Int# pattern False# :: Bool# pattern False# = Bool 0# pattern True# :: Bool# pattern True# = Bool 1# (==#) :: Int# -> Int# -> Bool# (==##) :: Double# -> Double# -> Bool#

### comment:10 Changed 3 years ago by

See #12767:

import qualified Control.Monad.Cont as C import Control.Monad.Cont hiding (runCont) import qualified Control.Monad.Writer as W import Control.Monad.Writer hiding (runWriter) import qualified Control.Monad.Reader as R import Control.Monad.Reader hiding (runReader) import qualified Control.Monad.State as S import Control.Monad.State hiding (runState) pattern Cont :: ((a -> r) -> r) -> Cont r a pattern Cont {runCont} <- (C.runCont -> runCont) where Cont a = cont a pattern Writer :: (a, w) -> Writer w a pattern Writer {runWriter} <- (W.runWriter -> runWriter) where Writer a = WriterT (Identity a) pattern Reader :: (r -> a) -> Reader r a pattern Reader {runReader} <- (R.runReader -> runReader) where Reader a = reader a pattern State :: (s -> (a, s)) -> State s a pattern State {runState} <- (S.runState -> runState) where State a = state a

### comment:11 Changed 3 years ago by

pattern NonEmpty :: NonEmpty a -> [a] pattern NonEmpty xs <- (nonEmpty -> Just xs) where NonEmpty xs = toList xs {-# COMPLETE [], NonEmpty #-}

using PatternSynonyms/CompleteSigs.

## Use

The pattern of destructing a `NonEmpty`

value only to reassemble it pops up a lot when using `NonEmpty`

f [] = ... f (x:xs) = ... (x:|xs)

An example is

-- match_groups :: [[(PatGroup,EquationInfo)]] -> DsM (NonEmpty MatchResult) -- match_groups [] = matchEmpty v ty -- match_groups (g:gs) = mapM match_group (g:|gs) match_groups :: [[(PatGroup,EquationInfo)]] -> DsM (NonEmpty MatchResult) match_groups [] = matchEmpty v ty match_groups (NonEmpty gs) = mapM match_group gs

Which can be abstracted into this rather useful eliminator:

-- match_groups = neElim (matchEmpty v ty) (mapM match_group) neElim :: c -> (NonEmpty a -> c) -> [a] -> c neElim c _ [] = c neElim _ f (NonEmpty xs) = f xs

## Use

Can be used twice in

snocView :: forall a. [a] -> Maybe ([a],a) snocView [] = Nothing snocView (x:xs) = go [] (x:|xs) where go :: [a] -> NonEmpty a -> Maybe ([a], a) go acc = \case x:|[] -> Just (reverse acc, x) x:|y:ys -> go (x:acc) (y:|ys)

which becomes

snocView :: forall a. [a] -> Maybe ([a],a) snocView [] = Nothing snocView (NonEmpty xs) = go [] xs where go :: [a] -> NonEmpty a -> Maybe ([a], a) go acc = \case x :|[] -> Just (reverse acc, x) x :|NonEmpty ys -> go (x:acc) ys

or

snocView :: forall a. [a] -> Maybe ([a],a) snocView = neElim Nothing (go []) where go :: [a] -> NonEmpty a -> Maybe ([a], a) go acc (x:|xs) = neElim (Just (reverse acc, x)) (go (x:acc)) xs

using `neElim`

.

## Use

-- gcd' (x :| y:ys) = gcd x (gcd' (y:|ys)) gcd' :: GCDDomain r => NonEmpty r -> r gcd' (x :| []) = normalize x gcd' (x :| [y]) = gcd x y gcd' (x :| NonEmpty ys) = gcd x (gcd' ys)

## Use

-- foldMapWithKey1 f (Node a (x:xs)) = f empty a <> foldMapWithKey1 (foldMapWithKey1 . fmap f . flip (|>)) (x:|xs) foldMapWithKey1 f (Node a (NonEmpty xs)) = f empty a <> foldMapWithKey1 (foldMapWithKey1 . fmap f . flip (|>)) xs -- traverseWithKey1 f (Node a (x:xs)) = Node <$> f empty a <.> traverseWithKey1 (traverseWithKey1 . fmap f . flip (|>)) (x:|xs) traverseWithKey1 f (Node a (NonEmpty xs)) = Node <$> f empty a <.> traverseWithKey1 (traverseWithKey1 . fmap f . flip (|>)) xs -- foldMapWithKey1 f (x:|(y:ys)) = f 0 x <> foldMapWithKey1 (f . (+1)) (y:|ys) foldMapWithKey1 f (x:|NonEmpty xs) = f 0 x <> foldMapWithKey1 (f . (+1)) xs -- unwrap (_ :| []) = Nothing -- unwrap (_ :| (a : as)) = Just (a :| as) unwrap (_ :| []) = Nothing unwrap (_ :| NonEmpty as) = Just as

or

unwrap (_ :| xs) = neElim Nothing Just xs

## Use

parseJSON = withArray "NonEmpty [a]" $ \v -> case toList v of [] -> fail "Non-empty list required" -- (x:xs) -> mapM parseJSON (x :| xs) NonEmpty xs -> mapM parseJSON xs

or

parseJSON = withArray "NonEmpty [a]" $ neElim (fail "Non-empty list required") (mapM parseJSON)

If we use `neElim`

that eliminates a `Foldable`

.

## Use

-- stitch (x0:|(x1:xs)) y = x0 <| stitch (x1:|xs) y stitch (x0:|NonEmpty xs) y = x0 <| stitch xs y

### comment:12 Changed 2 years ago by

Description: | modified (diff) |
---|

### comment:14 Changed 22 months ago by

Description: | modified (diff) |
---|

### comment:15 Changed 9 months ago by

Description: | modified (diff) |
---|

### comment:16 Changed 9 months ago by

Description: | modified (diff) |
---|

**Note:**See TracTickets for help on using tickets.

also if GHC allowed unlifted types in pattern synonyms, should that be a ticket?