{-# LANGUAGE CPP #-} {-# LANGUAGE Rank2Types #-} #ifdef TRUSTWORTHY {-# LANGUAGE Trustworthy #-} #endif #include "lens-common.h" ------------------------------------------------------------------------------- -- | -- Module : Control.Lens.Prism -- Copyright : (C) 2012-16 Edward Kmett -- License : BSD-style (see the file LICENSE) -- Maintainer : Edward Kmett -- Stability : provisional -- Portability : non-portable -- ------------------------------------------------------------------------------- module Control.Lens.Prism ( -- * Prisms Prism, Prism' , APrism, APrism' -- * Constructing Prisms , prism , prism' -- * Consuming Prisms , withPrism , clonePrism , outside , aside , without , below , isn't , matching , matching' -- * Common Prisms , _Left , _Right , _Just , _Nothing , _Void , _Show , only , nearly , Prefixed(..) , Suffixed(..) -- * Prismatic profunctors , Choice(..) ) where import Prelude () import Control.Applicative import qualified Control.Lens.Internal.List as List import Control.Lens.Internal.Prism import Control.Lens.Internal.Prelude import Control.Lens.Lens import Control.Lens.Review import Control.Lens.Type import Control.Monad import qualified Data.ByteString as BS import qualified Data.ByteString.Lazy as BL import qualified Data.List as List import Data.Profunctor.Rep import qualified Data.Text as TS import qualified Data.Text.Lazy as TL -- $setup -- >>> :set -XNoOverloadedStrings -- >>> import Control.Lens -- >>> import Numeric.Natural -- >>> import Debug.SimpleReflect.Expr -- >>> import Debug.SimpleReflect.Vars as Vars hiding (f,g) -- >>> let isLeft (Left _) = True; isLeft _ = False -- >>> let isRight (Right _) = True; isRight _ = False -- >>> let f :: Expr -> Expr; f = Debug.SimpleReflect.Vars.f -- >>> let g :: Expr -> Expr; g = Debug.SimpleReflect.Vars.g ------------------------------------------------------------------------------ -- Prism Internals ------------------------------------------------------------------------------ -- | If you see this in a signature for a function, the function is expecting a 'Prism'. type APrism s t a b = Market a b a (Identity b) -> Market a b s (Identity t) -- | @ -- type APrism' = 'Simple' 'APrism' -- @ type APrism' s a = APrism s s a a -- | Convert 'APrism' to the pair of functions that characterize it. withPrism :: APrism s t a b -> ((b -> t) -> (s -> Either t a) -> r) -> r withPrism k f = case coerce (k (Market Identity Right)) of Market bt seta -> f bt seta {-# INLINE withPrism #-} -- | Clone a 'Prism' so that you can reuse the same monomorphically typed 'Prism' for different purposes. -- -- See 'Control.Lens.Lens.cloneLens' and 'Control.Lens.Traversal.cloneTraversal' for examples of why you might want to do this. clonePrism :: APrism s t a b -> Prism s t a b clonePrism k = withPrism k $ \bt sta -> prism bt sta {-# INLINE clonePrism #-} ------------------------------------------------------------------------------ -- Prism Combinators ------------------------------------------------------------------------------ -- | Build a 'Control.Lens.Prism.Prism'. -- -- @'Either' t a@ is used instead of @'Maybe' a@ to permit the types of @s@ and @t@ to differ. -- prism :: (b -> t) -> (s -> Either t a) -> Prism s t a b prism bt seta = dimap seta (either pure (fmap bt)) . right' {-# INLINE prism #-} -- | This is usually used to build a 'Prism'', when you have to use an operation like -- 'Data.Typeable.cast' which already returns a 'Maybe'. prism' :: (b -> s) -> (s -> Maybe a) -> Prism s s a b prism' bs sma = prism bs (\s -> maybe (Left s) Right (sma s)) {-# INLINE prism' #-} -- | Use a 'Prism' as a kind of first-class pattern. -- -- @'outside' :: 'Prism' s t a b -> 'Lens' (t -> r) (s -> r) (b -> r) (a -> r)@ -- TODO: can we make this work with merely Strong? outside :: Representable p => APrism s t a b -> Lens (p t r) (p s r) (p b r) (p a r) outside k = withPrism k $ \bt seta f ft -> f (lmap bt ft) <&> \fa -> tabulate $ either (sieve ft) (sieve fa) . seta {-# INLINE outside #-} -- | Given a pair of prisms, project sums. -- -- Viewing a 'Prism' as a co-'Lens', this combinator can be seen to be dual to 'Control.Lens.Lens.alongside'. without :: APrism s t a b -> APrism u v c d -> Prism (Either s u) (Either t v) (Either a c) (Either b d) without k k' = withPrism k $ \bt seta -> withPrism k' $ \dv uevc -> prism (bimap bt dv) $ \su -> case su of Left s -> bimap Left Left (seta s) Right u -> bimap Right Right (uevc u) {-# INLINE without #-} -- | Use a 'Prism' to work over part of a structure. -- aside :: APrism s t a b -> Prism (e, s) (e, t) (e, a) (e, b) aside k = withPrism k $ \bt seta -> prism (fmap bt) $ \(e,s) -> case seta s of Left t -> Left (e,t) Right a -> Right (e,a) {-# INLINE aside #-} -- | 'lift' a 'Prism' through a 'Traversable' functor, giving a Prism that matches only if all the elements of the container match the 'Prism'. -- -- >>> [Left 1, Right "foo", Left 4, Right "woot"]^..below _Right -- [] -- -- >>> [Right "hail hydra!", Right "foo", Right "blah", Right "woot"]^..below _Right -- [["hail hydra!","foo","blah","woot"]] below :: Traversable f => APrism' s a -> Prism' (f s) (f a) below k = withPrism k $ \bt seta -> prism (fmap bt) $ \s -> case traverse seta s of Left _ -> Left s Right t -> Right t {-# INLINE below #-} -- | Check to see if this 'Prism' doesn't match. -- -- >>> isn't _Left (Right 12) -- True -- -- >>> isn't _Left (Left 12) -- False -- -- >>> isn't _Empty [] -- False -- -- @ -- 'isn't' = 'not' . 'Control.Lens.Extra.is' -- 'isn't' = 'hasn't' -- @ isn't :: APrism s t a b -> s -> Bool isn't k s = case matching k s of Left _ -> True Right _ -> False {-# INLINE isn't #-} -- | Retrieve the value targeted by a 'Prism' or return the -- original value while allowing the type to change if it does -- not match. -- -- >>> matching _Just (Just 12) -- Right 12 -- -- >>> matching _Just (Nothing :: Maybe Int) :: Either (Maybe Bool) Int -- Left Nothing matching :: APrism s t a b -> s -> Either t a matching k = withPrism k $ \_ seta -> seta {-# INLINE matching #-} -- | Like 'matching', but also works for combinations of 'Lens' and 'Prism's, -- and also 'Traversal's. -- -- >>> matching' (_2 . _Just) ('x', Just True) -- Right True -- -- >>> matching' (_2 . _Just) ('x', Nothing :: Maybe Int) :: Either (Char, Maybe Bool) Int -- Left ('x',Nothing) -- -- >>> matching' traverse "" :: Either [Int] Char -- Left [] -- -- >>> matching' traverse "xyz" :: Either [Int] Char -- Right 'x' matching' :: LensLike (Either a) s t a b -> s -> Either t a matching' k = either Right Left . k Left {-# INLINE matching' #-} ------------------------------------------------------------------------------ -- Common Prisms ------------------------------------------------------------------------------ -- | This 'Prism' provides a 'Traversal' for tweaking the 'Left' half of an 'Either': -- -- >>> over _Left (+1) (Left 2) -- Left 3 -- -- >>> over _Left (+1) (Right 2) -- Right 2 -- -- >>> Right 42 ^._Left :: String -- "" -- -- >>> Left "hello" ^._Left -- "hello" -- -- It also can be turned around to obtain the embedding into the 'Left' half of an 'Either': -- -- >>> _Left # 5 -- Left 5 -- -- >>> 5^.re _Left -- Left 5 _Left :: Prism (Either a c) (Either b c) a b _Left = prism Left $ either Right (Left . Right) {-# INLINE _Left #-} -- | This 'Prism' provides a 'Traversal' for tweaking the 'Right' half of an 'Either': -- -- >>> over _Right (+1) (Left 2) -- Left 2 -- -- >>> over _Right (+1) (Right 2) -- Right 3 -- -- >>> Right "hello" ^._Right -- "hello" -- -- >>> Left "hello" ^._Right :: [Double] -- [] -- -- It also can be turned around to obtain the embedding into the 'Right' half of an 'Either': -- -- >>> _Right # 5 -- Right 5 -- -- >>> 5^.re _Right -- Right 5 _Right :: Prism (Either c a) (Either c b) a b _Right = prism Right $ either (Left . Left) Right {-# INLINE _Right #-} -- | This 'Prism' provides a 'Traversal' for tweaking the target of the value of 'Just' in a 'Maybe'. -- -- >>> over _Just (+1) (Just 2) -- Just 3 -- -- Unlike 'Data.Traversable.traverse' this is a 'Prism', and so you can use it to inject as well: -- -- >>> _Just # 5 -- Just 5 -- -- >>> 5^.re _Just -- Just 5 -- -- Interestingly, -- -- @ -- m '^?' '_Just' ≡ m -- @ -- -- >>> Just x ^? _Just -- Just x -- -- >>> Nothing ^? _Just -- Nothing _Just :: Prism (Maybe a) (Maybe b) a b _Just = prism Just $ maybe (Left Nothing) Right {-# INLINE _Just #-} -- | This 'Prism' provides the 'Traversal' of a 'Nothing' in a 'Maybe'. -- -- >>> Nothing ^? _Nothing -- Just () -- -- >>> Just () ^? _Nothing -- Nothing -- -- But you can turn it around and use it to construct 'Nothing' as well: -- -- >>> _Nothing # () -- Nothing _Nothing :: Prism' (Maybe a) () _Nothing = prism' (const Nothing) $ maybe (Just ()) (const Nothing) {-# INLINE _Nothing #-} -- | 'Void' is a logically uninhabited data type. -- -- This is a 'Prism' that will always fail to match. _Void :: Prism s s a Void _Void = prism absurd Left {-# INLINE _Void #-} -- | This 'Prism' compares for exact equality with a given value. -- -- >>> only 4 # () -- 4 -- -- >>> 5 ^? only 4 -- Nothing only :: Eq a => a -> Prism' a () only a = prism' (\() -> a) $ guard . (a ==) {-# INLINE only #-} -- | This 'Prism' compares for approximate equality with a given value and a predicate for testing, -- an example where the value is the empty list and the predicate checks that a list is empty (same -- as 'Control.Lens.Empty._Empty' with the 'Control.Lens.Empty.AsEmpty' list instance): -- -- >>> nearly [] null # () -- [] -- >>> [1,2,3,4] ^? nearly [] null -- Nothing -- -- @'nearly' [] 'Prelude.null' :: 'Prism'' [a] ()@ -- -- To comply with the 'Prism' laws the arguments you supply to @nearly a p@ are somewhat constrained. -- -- We assume @p x@ holds iff @x ≡ a@. Under that assumption then this is a valid 'Prism'. -- -- This is useful when working with a type where you can test equality for only a subset of its -- values, and the prism selects such a value. nearly :: a -> (a -> Bool) -> Prism' a () nearly a p = prism' (\() -> a) $ guard . p {-# INLINE nearly #-} -- | This is an improper prism for text formatting based on 'Read' and 'Show'. -- -- This 'Prism' is \"improper\" in the sense that it normalizes the text formatting, but round tripping -- is idempotent given sane 'Read'/'Show' instances. -- -- >>> _Show # 2 -- "2" -- -- >>> "EQ" ^? _Show :: Maybe Ordering -- Just EQ -- -- @ -- '_Show' ≡ 'prism'' 'show' 'readMaybe' -- @ _Show :: (Read a, Show a) => Prism' String a _Show = prism show $ \s -> case reads s of [(a,"")] -> Right a _ -> Left s {-# INLINE _Show #-} class Prefixed t where -- | A 'Prism' stripping a prefix from a sequence when used as a 'Traversal', -- or prepending that prefix when run backwards: -- -- >>> "preview" ^? prefixed "pre" -- Just "view" -- -- >>> "review" ^? prefixed "pre" -- Nothing -- -- >>> prefixed "pre" # "amble" -- "preamble" prefixed :: t -> Prism' t t instance Eq a => Prefixed [a] where prefixed ps = prism' (ps ++) (List.stripPrefix ps) {-# INLINE prefixed #-} instance Prefixed TS.Text where prefixed p = prism' (p <>) (TS.stripPrefix p) {-# INLINE prefixed #-} instance Prefixed TL.Text where prefixed p = prism' (p <>) (TL.stripPrefix p) {-# INLINE prefixed #-} instance Prefixed BS.ByteString where prefixed p = prism' (p <>) (BS.stripPrefix p) {-# INLINE prefixed #-} instance Prefixed BL.ByteString where prefixed p = prism' (p <>) (BL.stripPrefix p) {-# INLINE prefixed #-} class Suffixed t where -- | A 'Prism' stripping a suffix from a sequence when used as a 'Traversal', -- or appending that suffix when run backwards: -- -- >>> "review" ^? suffixed "view" -- Just "re" -- -- >>> "review" ^? suffixed "tire" -- Nothing -- -- >>> suffixed ".o" # "hello" -- "hello.o" suffixed :: t -> Prism' t t instance Eq a => Suffixed [a] where suffixed qs = prism' (++ qs) (List.stripSuffix qs) {-# INLINE suffixed #-} instance Suffixed TS.Text where suffixed qs = prism' (<> qs) (TS.stripSuffix qs) {-# INLINE suffixed #-} instance Suffixed TL.Text where suffixed qs = prism' (<> qs) (TL.stripSuffix qs) {-# INLINE suffixed #-} instance Suffixed BS.ByteString where suffixed qs = prism' (<> qs) (BS.stripSuffix qs) {-# INLINE suffixed #-} instance Suffixed BL.ByteString where suffixed qs = prism' (<> qs) (BL.stripSuffix qs) {-# INLINE suffixed #-}