optics-core-0.3: src/Optics/Cons/Core.hs
-- |
-- Module: Optics.Cons.Core
-- Description: Optics to access the left or right element of a container.
--
-- This module defines the 'Cons' and 'Snoc' classes, which provide 'Prism's for
-- the leftmost and rightmost elements of a container, respectively.
--
-- Note that orphan instances for these classes are defined in the @Optics.Cons@
-- module from @optics-extra@, so if you are not simply depending on @optics@
-- you may wish to import that module instead.
--
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
module Optics.Cons.Core
(
-- * Cons
Cons(..)
, (<|)
, cons
, uncons
, _head, _tail
, pattern (:<)
-- * Snoc
, Snoc(..)
, (|>)
, snoc
, unsnoc
, _init, _last
, pattern (:>)
) where
import Control.Applicative (ZipList(..))
import Data.Coerce
import Data.Sequence hiding ((<|), (|>), (:<), (:>))
import qualified Data.Sequence as Seq
import Data.Tuple.Optics
import Optics.AffineFold
import Optics.AffineTraversal
import Optics.Coerce
import Optics.Internal.Utils
import Optics.Optic
import Optics.Prism
import Optics.Review
infixr 5 <|, `cons`
infixl 5 |>, `snoc`
-- | Pattern synonym for matching on the leftmost element of a structure.
--
-- >>> case ['a','b','c'] of (x :< _) -> x
-- 'a'
--
pattern (:<) :: forall s a. Cons s s a a => a -> s -> s
pattern (:<) a s <- (preview _Cons -> Just (a, s)) where
(:<) a s = review _Cons (a, s)
infixr 5 :<
infixl 5 :>
-- | Pattern synonym for matching on the rightmost element of a structure.
--
-- >>> case ['a','b','c'] of (_ :> x) -> x
-- 'c'
--
pattern (:>) :: forall s a. Snoc s s a a => s -> a -> s
pattern (:>) s a <- (preview _Snoc -> Just (s, a)) where
(:>) a s = review _Snoc (a, s)
------------------------------------------------------------------------------
-- Cons
------------------------------------------------------------------------------
-- | This class provides a way to attach or detach elements on the left
-- side of a structure in a flexible manner.
class Cons s t a b | s -> a, t -> b, s b -> t, t a -> s where
-- |
--
-- @
-- '_Cons' :: 'Prism' [a] [b] (a, [a]) (b, [b])
-- '_Cons' :: 'Prism' ('Seq' a) ('Seq' b) (a, 'Seq' a) (b, 'Seq' b)
-- '_Cons' :: 'Prism' (Vector a) (Vector b) (a, Vector a) (b, Vector b)
-- '_Cons' :: 'Prism'' 'String' ('Char', 'String')
-- '_Cons' :: 'Prism'' Text ('Char', Text)
-- '_Cons' :: 'Prism'' ByteString ('Data.Word.Word8', ByteString)
-- @
_Cons :: Prism s t (a, s) (b, t)
instance Cons [a] [b] a b where
_Cons = prism (uncurry' (:)) $ \aas -> case aas of
(a:as) -> Right (a, as)
[] -> Left []
{-# INLINE _Cons #-}
instance Cons (ZipList a) (ZipList b) a b where
_Cons = coerceS . coerceT . coerceA . coerceB $ listCons
where
listCons :: Prism [a] [b] (a, [a]) (b, [b])
listCons = _Cons
{-# INLINE _Cons #-}
instance Cons (Seq a) (Seq b) a b where
_Cons = prism (uncurry' (Seq.<|)) $ \aas -> case viewl aas of
a Seq.:< as -> Right (a, as)
EmptyL -> Left mempty
{-# INLINE _Cons #-}
-- | 'cons' an element onto a container.
--
-- This is an infix alias for 'cons'.
--
-- >>> 1 <| []
-- [1]
--
-- >>> 'a' <| "bc"
-- "abc"
--
-- >>> 1 <| []
-- [1]
--
-- >>> 1 <| [2, 3]
-- [1,2,3]
(<|) :: Cons s s a a => a -> s -> s
(<|) = curry (review _Cons)
{-# INLINE (<|) #-}
-- | 'cons' an element onto a container.
--
-- >>> cons 'a' ""
-- "a"
--
-- >>> cons 'a' "bc"
-- "abc"
cons :: Cons s s a a => a -> s -> s
cons = curry (review _Cons)
{-# INLINE cons #-}
-- | Attempt to extract the left-most element from a container, and a version of
-- the container without that element.
--
-- >>> uncons []
-- Nothing
--
-- >>> uncons [1, 2, 3]
-- Just (1,[2,3])
uncons :: Cons s s a a => s -> Maybe (a, s)
uncons = preview _Cons
{-# INLINE uncons #-}
-- | An 'AffineTraversal' reading and writing to the 'head' of a /non-empty/
-- container.
--
-- >>> "abc" ^? _head
-- Just 'a'
--
-- >>> "abc" & _head .~ 'd'
-- "dbc"
--
-- >>> [1,2,3] & _head %~ (*10)
-- [10,2,3]
--
-- >>> [] & _head %~ absurd
-- []
--
-- >>> [1,2,3] ^? _head
-- Just 1
--
-- >>> [] ^? _head
-- Nothing
--
-- >>> [1,2] ^? _head
-- Just 1
--
-- >>> [] & _head .~ 1
-- []
--
-- >>> [0] & _head .~ 2
-- [2]
--
-- >>> [0,1] & _head .~ 2
-- [2,1]
_head :: Cons s s a a => AffineTraversal' s a
_head = _Cons % _1
{-# INLINE _head #-}
-- | An 'AffineTraversal' reading and writing to the 'tail' of a /non-empty/
-- container.
--
-- >>> "ab" & _tail .~ "cde"
-- "acde"
--
-- >>> [] & _tail .~ [1,2]
-- []
--
-- >>> [1,2,3,4,5] & _tail % traversed %~ (*10)
-- [1,20,30,40,50]
--
-- >>> [1,2] & _tail .~ [3,4,5]
-- [1,3,4,5]
--
-- >>> [] & _tail .~ [1,2]
-- []
--
-- >>> "abc" ^? _tail
-- Just "bc"
--
-- >>> "hello" ^? _tail
-- Just "ello"
--
-- >>> "" ^? _tail
-- Nothing
_tail :: Cons s s a a => AffineTraversal' s s
_tail = _Cons % _2
{-# INLINE _tail #-}
------------------------------------------------------------------------------
-- Snoc
------------------------------------------------------------------------------
-- | This class provides a way to attach or detach elements on the right side of
-- a structure in a flexible manner.
class Snoc s t a b | s -> a, t -> b, s b -> t, t a -> s where
_Snoc :: Prism s t (s, a) (t, b)
instance Snoc [a] [b] a b where
_Snoc = prism (\(as,a) -> as Prelude.++ [a]) $ \aas -> if Prelude.null aas
then Left []
else Right (Prelude.init aas, Prelude.last aas)
{-# INLINE _Snoc #-}
instance Snoc (ZipList a) (ZipList b) a b where
_Snoc = withPrism listSnoc $ \listReview listPreview ->
prism (coerce listReview) (coerce listPreview) where
listSnoc :: Prism [a] [b] ([a], a) ([b], b)
listSnoc = _Snoc
{-# INLINE _Snoc #-}
instance Snoc (Seq a) (Seq b) a b where
_Snoc = prism (uncurry' (Seq.|>)) $ \aas -> case viewr aas of
as Seq.:> a -> Right (as, a)
EmptyR -> Left mempty
{-# INLINE _Snoc #-}
-- | An 'AffineTraversal' reading and replacing all but the a last element of a
-- /non-empty/ container.
--
-- >>> "abcd" ^? _init
-- Just "abc"
--
-- >>> "" ^? _init
-- Nothing
--
-- >>> "ab" & _init .~ "cde"
-- "cdeb"
--
-- >>> [] & _init .~ [1,2]
-- []
--
-- >>> [1,2,3,4] & _init % traversed %~ (*10)
-- [10,20,30,4]
--
-- >>> [1,2,3] ^? _init
-- Just [1,2]
--
-- >>> "hello" ^? _init
-- Just "hell"
--
-- >>> [] ^? _init
-- Nothing
_init :: Snoc s s a a => AffineTraversal' s s
_init = _Snoc % _1
{-# INLINE _init #-}
-- | An 'AffineTraversal' reading and writing to the last element of a
-- /non-empty/ container.
--
-- >>> "abc" ^? _last
-- Just 'c'
--
-- >>> "" ^? _last
-- Nothing
--
-- >>> [1,2,3] & _last %~ (+1)
-- [1,2,4]
--
-- >>> [1,2] ^? _last
-- Just 2
--
-- >>> [] & _last .~ 1
-- []
--
-- >>> [0] & _last .~ 2
-- [2]
--
-- >>> [0,1] & _last .~ 2
-- [0,2]
_last :: Snoc s s a a => AffineTraversal' s a
_last = _Snoc % _2
{-# INLINE _last #-}
-- | 'snoc' an element onto the end of a container.
--
-- This is an infix alias for 'snoc'.
--
-- >>> "" |> 'a'
-- "a"
--
-- >>> "bc" |> 'a'
-- "bca"
(|>) :: Snoc s s a a => s -> a -> s
(|>) = curry (review _Snoc)
{-# INLINE (|>) #-}
-- | 'snoc' an element onto the end of a container.
--
-- >>> snoc "hello" '!'
-- "hello!"
snoc :: Snoc s s a a => s -> a -> s
snoc = curry (review _Snoc)
{-# INLINE snoc #-}
-- | Attempt to extract the right-most element from a container, and a version
-- of the container without that element.
--
-- >>> unsnoc "hello!"
-- Just ("hello",'!')
--
-- >>> unsnoc ""
-- Nothing
unsnoc :: Snoc s s a a => s -> Maybe (s, a)
unsnoc s = preview _Snoc s
{-# INLINE unsnoc #-}
-- $setup
-- >>> import Data.Void
-- >>> import Optics.Core