Opened 3 years ago

# RFC: Add pattern synonyms to base

Reported by: Owned by: Iceland_jack normal libraries/base 7.10.3 Unknown/Multiple Unknown/Multiple None/Unknown

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

### comment:1 Changed 3 years ago by Iceland_jack

```pattern II :: Int -> Int#
pattern II i      <- (I# -> i)
where II (I# i) = i

pattern FF :: Float -> Float#
pattern FF f      <- (F# -> f)
where FF (F# f) = f
```

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

```tImB.hs:6:22-25: error: …
• Expecting a lifted type, but ‘Int#’ is unlifted
• In the type ‘Int#’
tImB.hs:10:22-27: error: …
• Expecting a lifted type, but ‘Float#’ is unlifted
• In the type ‘Float#’
Compilation failed.
```

### comment:2 Changed 3 years ago by Iceland_jack

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

Last edited 3 years ago by Iceland_jack (previous) (diff)

### comment:3 Changed 3 years ago by nomeata

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 ekmett

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 Iceland_jack

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

Last edited 3 years ago by Iceland_jack (previous) (diff)

### comment:6 Changed 3 years ago by Iceland_jack

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
```
Last edited 3 years ago by Iceland_jack (previous) (diff)

### comment:7 Changed 3 years ago by Iceland_jack

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.

```pattern Read :: Read a => a -> String
```

with the caveat that they parse different types,

```foo :: String -> String
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 @()      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]
```
Last edited 3 years ago by Iceland_jack (previous) (diff)

### comment:8 Changed 3 years ago by Iceland_jack

Not sure if makes sense

### Control.DeepSeq

```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)
```
Last edited 2 years ago by Iceland_jack (previous) (diff)

### comment:9 Changed 3 years ago by Iceland_jack

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#
```
Last edited 2 years ago by Iceland_jack (previous) (diff)

### comment:10 Changed 3 years ago by Iceland_jack

See #12767:

```import qualified Control.Monad.Cont   as C

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 State :: (s -> (a, s)) -> State s a
pattern State {runState} <- (S.runState -> runState)
where State a           = state a
```
Last edited 3 years ago by Iceland_jack (previous) (diff)

### comment:11 Changed 3 years ago by Iceland_jack

```pattern NonEmpty :: NonEmpty a -> [a]
pattern NonEmpty xs <- (nonEmpty -> Just xs)
where NonEmpty xs = toList xs

{-# COMPLETE [], NonEmpty  #-}
```

## 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
```
Last edited 3 years ago by Iceland_jack (previous) (diff)

### comment:12 Changed 2 years ago by Iceland_jack

Description: modified (diff)