fresnel-0.1.0.0: src/Fresnel/Iso.hs
{-# LANGUAGE RankNTypes #-}
{-|
@'Iso'@s are the root of the optic hierarchy: an @'Iso'@ can be used anywhere any other kind of optic is required. On the other hand, if something requests an @'Iso'@, it can only be given an @'Iso'@, as it doesn't provide enough capabilities to accept anything else.
This implies that they're the weakest optic; they make the fewest assumptions, and thus can provide only the most minimal guarantees. Even so, these guarantees are relativevly strong: notionally, an @'Iso'@ consists of functions @/f/@ and @/g/@ which are mutual inverses:
@
f '.' g = 'id'
@
@
g '.' f = 'id'
@
-}
module Fresnel.Iso
( -- * Isos
Iso
, Iso'
, IsIso
-- * Construction
, iso
, from
-- * Elimination
, withIso
, under
-- * Functions
, constant
, constantWith
, involuted
, flipped
, curried
, uncurried
, au
, auf
-- * Relations
, non
, non'
-- * Tuples
, swapped
, mirrored
-- * Coercion
, coerced
, coercedTo
, coercedFrom
-- * Functor
, fmapping
-- * Contravariant
, contramapping
-- * Bifunctor
, bimapping
, firsting
, seconding
-- * Profunctor
, dimapping
, lmapping
, rmapping
-- * (Co-)representable
, protabulated
, cotabulated
) where
import Control.Applicative (Alternative)
import Control.Monad (guard)
import Data.Bifunctor
import Data.Coerce (Coercible, coerce)
import Data.Functor.Contravariant
import Data.Maybe (fromMaybe)
import Data.Profunctor
import Data.Profunctor.Rep hiding (cotabulated)
import Data.Profunctor.Sieve
import Data.Tuple (swap)
import Fresnel.Iso.Internal
import Fresnel.Optic
import Fresnel.Optional (isn't)
import Fresnel.Prism (Prism', only)
import Fresnel.Profunctor.Coexp
import Fresnel.Review (review)
-- Isos
type Iso s t a b = forall p . IsIso p => Optic p s t a b
type Iso' s a = Iso s s a a
-- Construction
iso :: (s -> a) -> (b -> t) -> Iso s t a b
iso f g = f `dimap` g
from :: Iso s t a b -> Iso b a t s
from o = withIso o (flip dimap)
-- Elimination
withIso :: Iso s t a b -> (((s -> a) -> (b -> t) -> r) -> r)
withIso i = withCoexp (i mempty) . flip
under :: Iso s t a b -> (t -> s) -> (b -> a)
under i = withIso i (\ f r -> (f .) . (. r))
-- Functions
constant :: a -> Iso (a -> b) (a' -> b') b b'
constant a = a `constantWith` const
constantWith :: a -> (b' -> a' -> b') -> Iso (a -> b) (a' -> b') b b'
constantWith a = iso ($ a)
involuted :: (a -> a) -> Iso' a a
involuted f = iso f f
flipped :: Iso (a -> b -> c) (a' -> b' -> c') (b -> a -> c) (b' -> a' -> c')
flipped = iso flip flip
curried :: Iso ((a, b) -> c) ((a', b') -> c') (a -> b -> c) (a' -> b' -> c')
curried = iso curry uncurry
uncurried :: Iso (a -> b -> c) (a' -> b' -> c') ((a, b) -> c) ((a', b') -> c')
uncurried = iso uncurry curry
au :: Functor f => Iso s t a b -> (((b -> t) -> f s) -> f a)
au o f = withIso o (\ sa bt -> sa <$> f bt)
auf :: (Functor f, Functor g) => Iso s t a b -> ((f t -> g s) -> (f b -> g a))
auf o f fb = withIso o (\ sa bt -> sa <$> f (bt <$> fb))
-- Relations
non :: Eq a => a -> Iso' (Maybe a) a
non a = non' (only a)
non' :: Prism' a () -> Iso' (Maybe a) a
non' o = iso (fromMaybe (review o ())) (select (isn't o))
-- Tuples
swapped :: Iso (a, b) (a', b') (b, a) (b', a')
swapped = iso swap swap
mirrored :: Iso (Either a b) (Either a' b') (Either b a) (Either b' a')
mirrored = iso mirror mirror
where
mirror = either Right Left
-- Coercion
coerced :: (Coercible s a, Coercible t b) => Iso s t a b
coerced = coerce `iso` coerce
-- | Build a bidi coercion, taking a constructor for the type being built both to improve type inference and as documentation.
--
-- For example, given two newtypes @A@ and @B@ wrapping the same type, this expression:
--
-- @
-- 'coercedTo' B <<< 'coercedFrom' A
-- @
--
-- produces a bijection of type @'Iso'' A B@.
coercedTo :: Coercible t b => (s -> a) -> Iso s t a b
coercedTo f = f `iso` coerce
-- | Build a bidi coercion, taking a constructor for the type being eliminated both to improve type inference and as documentation.
--
-- For example, given two newtypes @A@ and @B@ wrapping the same type, this expression:
--
-- @
-- 'coercedTo' B <<< 'coercedFrom' A
-- @
--
-- produces a bijection of type @'Iso'' A B@.
coercedFrom :: Coercible s a => (b -> t) -> Iso s t a b
coercedFrom g = coerce `iso` g
-- Functor
fmapping :: (Functor f, Functor g) => Iso s t a b -> Iso (f s) (g t) (f a) (g b)
fmapping o = withIso o $ \ sa bt -> iso (fmap sa) (fmap bt)
-- Contravariant
contramapping :: (Contravariant f, Contravariant g) => Iso s t a b -> Iso (f a) (g b) (f s) (g t)
contramapping o = withIso o $ \ sa bt -> iso (contramap sa) (contramap bt)
-- Bifunctor
bimapping :: (Bifunctor p, Bifunctor q) => Iso s t a b -> Iso s' t' a' b' -> Iso (p s s') (q t t') (p a a') (q b b')
bimapping a b = withIso a $ \ lsa lbt -> withIso b $ \ rsa rbt -> iso (bimap lsa rsa) (bimap lbt rbt)
firsting :: (Bifunctor p, Bifunctor q) => Iso s t a b -> Iso (p s x) (q t y) (p a x) (q b y)
firsting a = withIso a $ \ sa bt -> iso (first sa) (first bt)
seconding :: (Bifunctor p, Bifunctor q) => Iso s t a b -> Iso (p x s) (q y t) (p x a) (q y b)
seconding b = withIso b $ \ sa bt -> iso (second sa) (second bt)
-- Profunctor
dimapping :: (Profunctor p, Profunctor q) => Iso s t a b -> Iso s' t' a' b' -> Iso (p a s') (q b t') (p s a') (q t b')
dimapping a b = withIso a $ \ lsa lbt -> withIso b $ \ rsa rbt -> iso (dimap lsa rsa) (dimap lbt rbt)
lmapping :: (Profunctor p, Profunctor q) => Iso s t a b -> Iso (p a x) (q b y) (p s x) (q t y)
lmapping a = withIso a $ \ lsa lbt -> iso (lmap lsa) (lmap lbt)
rmapping :: (Profunctor p, Profunctor q) => Iso s t a b -> Iso (p x s) (q y t) (p x a) (q y b)
rmapping b = withIso b $ \ rsa rbt -> iso (rmap rsa) (rmap rbt)
-- (Co-)representable (profunctorial)
protabulated :: (Representable p, Representable q) => Iso (a -> Rep p b) (a' -> Rep q b') (p a b) (q a' b')
protabulated = tabulate `iso` sieve
cotabulated :: (Corepresentable p, Corepresentable q) => Iso (Corep p a -> b) (Corep q a' -> b') (p a b) (q a' b')
cotabulated = cotabulate `iso` cosieve
-- Utilities
select :: Alternative f => (a -> Bool) -> (a -> f a)
select p a = a <$ guard (p a)