profunctor-optics-0.0.0.2: src/Data/Profunctor/Optic/Lens.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE QuantifiedConstraints #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
module Data.Profunctor.Optic.Lens (
-- * Lens & Ixlens
Lens
, Ixlens
, Lens'
, Ixlens'
, lens
, ixlens
, lensVl
, ixlensVl
, matching
, cloneLens
-- * Colens & Cxlens
, Colens
, Cxlens
, Colens'
, Cxlens'
, colens
--, cxlens
, colensVl
, comatching
--, cloneColens
-- * Optics
, ixfirst
, cofirst
, ixsecond
, cosecond
, united
, voided
, valued
, root
, branches
-- * Primitive operators
, withLens
, withIxlens
--, withColens
-- * Operators
, toPastro
, toTambara
-- * Carriers
, ALens
, ALens'
, AIxlens
, AIxlens'
, LensRep(..)
, IxlensRep(..)
-- , AColens
-- , AColens'
--, ColensRep(..)
-- * Classes
, Strong(..)
, Costrong(..)
) where
import Data.Profunctor.Strong
import Data.Profunctor.Optic.Iso
import Data.Profunctor.Optic.Import
import Data.Profunctor.Optic.Index
import Data.Profunctor.Optic.Type
import Data.Tree
import Data.Void (Void, absurd)
import GHC.Generics hiding (from, to)
import qualified Data.Bifunctor as B
-- $setup
-- >>> :set -XNoOverloadedStrings
-- >>> :set -XTypeApplications
-- >>> :set -XFlexibleContexts
-- >>> import Data.Tree
-- >>> import Data.Int.Instance
-- >>> :load Data.Profunctor.Optic
---------------------------------------------------------------------
-- 'Lens' & 'Ixlens'
---------------------------------------------------------------------
-- | Obtain a 'Lens' from a getter and setter.
--
-- /Caution/: In order for the generated optic to be well-defined,
-- you must ensure that the input functions satisfy the following
-- properties:
--
-- * @sa (sbt s a) ≡ a@
--
-- * @sbt s (sa s) ≡ s@
--
-- * @sbt (sbt s a1) a2 ≡ sbt s a2@
--
-- See 'Data.Profunctor.Optic.Property'.
--
lens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
lens sa sbt = dimap (id &&& sa) (uncurry sbt) . second'
{-# INLINE lens #-}
-- | Obtain an indexed 'Lens' from an indexed getter and a setter.
--
-- Compare 'lens' and 'Data.Profunctor.Optic.Traversal.ixtraversal'.
--
-- /Caution/: In order for the generated optic to be well-defined,
-- you must ensure that the input functions constitute a legal
-- indexed lens:
--
-- * @snd . sia (sbt s a) ≡ a@
--
-- * @sbt s (snd $ sia s) ≡ s@
--
-- * @sbt (sbt s a1) a2 ≡ sbt s a2@
--
-- See 'Data.Profunctor.Optic.Property'.
--
ixlens :: (s -> (i , a)) -> (s -> b -> t) -> Ixlens i s t a b
ixlens sia sbt = ixlensVl $ \iab s -> sbt s <$> uncurry iab (sia s)
{-# INLINE ixlens #-}
-- | Transform a Van Laarhoven lens into a profunctor lens.
--
-- Compare 'Data.Profunctor.Optic.Grate.grateVl' and 'Data.Profunctor.Optic.Traversal.traversalVl'.
--
-- /Caution/: In order for the generated optic to be well-defined,
-- you must ensure that the input satisfies the following properties:
--
-- * @abst Identity ≡ Identity@
--
-- * @fmap (abst f) . (abst g) ≡ getCompose . abst (Compose . fmap f . g)@
--
-- More generally, a profunctor optic must be monoidal as a natural
-- transformation:
--
-- * @o id ≡ id@
--
-- * @o ('Data.Profunctor.Composition.Procompose' p q) ≡ 'Data.Profunctor.Composition.Procompose' (o p) (o q)@
--
lensVl :: (forall f. Functor f => (a -> f b) -> s -> f t) -> Lens s t a b
lensVl o = dimap ((info &&& values) . o (flip Index id)) (uncurry id . swap) . first'
{-# INLINE lensVl #-}
-- | Transform an indexed Van Laarhoven lens into an indexed profunctor 'Lens'.
--
-- An 'Ixlens' is a valid 'Lens' and a valid 'IxTraversal'.
--
-- Compare 'lensVl' & 'Data.Profunctor.Optic.Traversal.ixtraversalVl'.
--
-- /Caution/: In order for the generated optic to be well-defined,
-- you must ensure that the input satisfies the following properties:
--
-- * @iabst (const Identity) ≡ Identity@
--
-- * @fmap (iabst $ const f) . (iabst $ const g) ≡ getCompose . iabst (const $ Compose . fmap f . g)@
--
-- More generally, a profunctor optic must be monoidal as a natural
-- transformation:
--
-- * @o id ≡ id@
--
-- * @o ('Data.Profunctor.Composition.Procompose' p q) ≡ 'Data.Profunctor.Composition.Procompose' (o p) (o q)@
--
-- See 'Data.Profunctor.Optic.Property'.
--
ixlensVl :: (forall f. Functor f => (i -> a -> f b) -> s -> f t) -> Ixlens i s t a b
ixlensVl f = lensVl $ \iab -> f (curry iab) . snd
{-# INLINE ixlensVl #-}
-- | Obtain a 'Lens' from its free tensor representation.
--
matching :: (s -> (c , a)) -> ((c , b) -> t) -> Lens s t a b
matching sca cbt = dimap sca cbt . second'
-- | TODO: Document
--
cloneLens :: ALens s t a b -> Lens s t a b
cloneLens o = withLens o lens
---------------------------------------------------------------------
-- 'Colens' & 'Cxlens'
---------------------------------------------------------------------
-- | Obtain a 'Colens' from a getter and setter.
--
-- @
-- 'colens' f g ≡ \\f g -> 're' ('lens' f g)
-- 'colens' bsia bt ≡ 'colensVl' '$' \\ts b -> bsia b '<$>' (ts . bt '$' b)
-- 'review' $ 'colens' f g ≡ f
-- 'set' . 're' $ 're' ('lens' f g) ≡ g
-- @
--
-- A 'Colens' is a 'Review', so you can specialise types to obtain:
--
-- @ 'review' :: 'Colens'' s a -> a -> s @
--
-- /Caution/: In addition to the normal optic laws, the input functions
-- must have the correct < https://wiki.haskell.org/Lazy_pattern_match laziness > annotations.
--
-- For example, this is a perfectly valid 'Colens':
--
-- @
-- co1 :: Colens a b (a, c) (b, c)
-- co1 = flip colens fst $ \ ~(_,y) b -> (b,y)
-- @
--
-- However removing the annotation will result in a faulty optic.
--
-- See 'Data.Profunctor.Optic.Property'.
--
colens :: (b -> s -> a) -> (b -> t) -> Colens s t a b
colens bsa bt = unsecond . dimap (uncurry bsa) (id &&& bt)
-- | Transform a Van Laarhoven colens into a profunctor colens.
--
-- Compare 'Data.Profunctor.Optic.Grate.grateVl'.
--
-- /Caution/: In addition to the normal optic laws, the input functions
-- must have the correct laziness annotations.
--
-- For example, this is a perfectly valid 'Colens':
--
-- @
-- co1 = colensVl $ \f ~(a,b) -> (,b) <$> f a
-- @
--
-- However removing the annotation will result in a faulty optic.
--
colensVl :: (forall f. Functor f => (t -> f s) -> b -> f a) -> Colens s t a b
colensVl o = unfirst . dimap (uncurry id . swap) ((info &&& values) . o (flip Index id))
-- | Obtain a 'Colens' from its free tensor representation.
--
comatching :: ((c , s) -> a) -> (b -> (c , t)) -> Colens s t a b
comatching csa bct = unsecond . dimap csa bct
---------------------------------------------------------------------
-- Primitive operators
---------------------------------------------------------------------
-- | Extract the two functions that characterize a 'Lens'.
--
withLens :: ALens s t a b -> ((s -> a) -> (s -> b -> t) -> r) -> r
withLens o f = case o (LensRep id (flip const)) of LensRep x y -> f x y
---------------------------------------------------------------------
-- Optics
---------------------------------------------------------------------
-- | TODO: Document
--
cofirst :: Colens a b (a , c) (b , c)
cofirst = unfirst
-- | TODO: Document
--
cosecond :: Colens a b (c , a) (c , b)
cosecond = unsecond
-- | TODO: Document
--
-- >>> ixlists (ix @Int traversed . ix first' . ix traversed) [("foo",1), ("bar",2)]
-- [(0,'f'),(1,'o'),(2,'o'),(0,'b'),(1,'a'),(2,'r')]
--
-- >>> ixlists (ix @Int traversed . ixfirst . ix traversed) [("foo",1), ("bar",2)]
-- [(0,'f'),(1,'o'),(2,'o'),(0,'b'),(1,'a'),(2,'r')]
--
-- >>> ixlists (ix @Int traversed % ix first' % ix traversed) [("foo",1), ("bar",2)]
-- [(0,'f'),(1,'o'),(2,'o'),(1,'b'),(2,'a'),(3,'r')]
--
-- >>> ixlists (ix @Int traversed % ixfirst % ix traversed) [("foo",1), ("bar",2)]
-- [(0,'f'),(1,'o'),(2,'o'),(2,'b'),(3,'a'),(4,'r')]
--
ixfirst :: Ixlens i (a , c) (b , c) a b
ixfirst = lmap assocl . first'
-- | TODO: Document
--
ixsecond :: Ixlens i (c , a) (c , b) a b
ixsecond = lmap (\(i, (c, a)) -> (c, (i, a))) . second'
-- | There is a `Unit` in everything.
--
-- >>> "hello" ^. united
-- ()
-- >>> "hello" & united .~ ()
-- "hello"
--
united :: Lens' a ()
united = lens (const ()) const
-- | There is everything in a `Void`.
--
-- >>> [] & fmapped . voided <>~ "Void"
-- []
-- >>> Nothing & fmapped . voided ..~ abs
-- Nothing
--
voided :: Lens' Void a
voided = lens absurd const
-- | TODO: Document
--
-- Compare 'Data.Profunctor.Optic.Prism.keyed'.
--
valued :: Eq k => k -> Lens' (k -> v) v
valued k = lens ($ k) (\g v' x -> if (k == x) then v' else g x)
-- | A 'Lens' that focuses on the root of a 'Tree'.
--
-- >>> view root $ Node 42 []
-- 42
--
root :: Lens' (Tree a) a
root = lensVl $ \f (Node a as) -> (`Node` as) <$> f a
{-# INLINE root #-}
-- | A 'Lens' returning the direct descendants of the root of a 'Tree'
--
-- @'Data.Profunctor.Optic.View.view' 'branches' ≡ 'subForest'@
--
branches :: Lens' (Tree a) [Tree a]
branches = lensVl $ \f (Node a as) -> Node a <$> f as
{-# INLINE branches #-}
---------------------------------------------------------------------
-- Operators
---------------------------------------------------------------------
-- | Use a 'Lens' to construct a 'Pastro'.
--
toPastro :: ALens s t a b -> p a b -> Pastro p s t
toPastro o p = withLens o $ \sa sbt -> Pastro (uncurry sbt . swap) p (\s -> (sa s, s))
-- | Use a 'Lens' to construct a 'Tambara'.
--
toTambara :: Strong p => ALens s t a b -> p a b -> Tambara p s t
toTambara o p = withLens o $ \sa sbt -> Tambara (first' . lens sa sbt $ p)
---------------------------------------------------------------------
-- LensRep
---------------------------------------------------------------------
-- | The `LensRep` profunctor precisely characterizes a 'Lens'.
--
data LensRep a b s t = LensRep (s -> a) (s -> b -> t)
type ALens s t a b = Optic (LensRep a b) s t a b
type ALens' s a = ALens s s a a
instance Profunctor (LensRep a b) where
dimap f g (LensRep sa sbt) = LensRep (sa . f) (\s -> g . sbt (f s))
instance Strong (LensRep a b) where
first' (LensRep sa sbt) =
LensRep (\(a, _) -> sa a) (\(s, c) b -> (sbt s b, c))
second' (LensRep sa sbt) =
LensRep (\(_, a) -> sa a) (\(c, s) b -> (c, sbt s b))
instance Sieve (LensRep a b) (Index a b) where
sieve (LensRep sa sbt) s = Index (sa s) (sbt s)
instance Representable (LensRep a b) where
type Rep (LensRep a b) = Index a b
tabulate f = LensRep (\s -> info (f s)) (\s -> values (f s))
---------------------------------------------------------------------
-- IxlensRep
---------------------------------------------------------------------
data IxlensRep i a b s t = IxlensRep (s -> (i , a)) (s -> b -> t)
type AIxlens i s t a b = IndexedOptic (IxlensRep i a b) i s t a b
type AIxlens' i s a = AIxlens i s s a a
instance Profunctor (IxlensRep i a b) where
dimap f g (IxlensRep sia sbt) = IxlensRep (sia . f) (\s -> g . sbt (f s))
instance Strong (IxlensRep i a b) where
first' (IxlensRep sia sbt) =
IxlensRep (\(a, _) -> sia a) (\(s, c) b -> (sbt s b, c))
second' (IxlensRep sia sbt) =
IxlensRep (\(_, a) -> sia a) (\(c, s) b -> (c, sbt s b))
-- | Extract the two functions that characterize a 'Lens'.
--
withIxlens :: Monoid i => AIxlens i s t a b -> ((s -> (i , a)) -> (s -> b -> t) -> r) -> r
withIxlens o f = case o (IxlensRep id $ flip const) of IxlensRep x y -> f (x . (mempty,)) (\s b -> y (mempty, s) b)