-- |
-- Module      : Lens.Explicit
-- Copyright   : (c) Justus SagemΓΌller 2017
-- License     : GPL v3
-- 
-- Maintainer  : (@) sagemueller $ geo.uni-koeln.de
-- Stability   : experimental
-- Portability : portable
-- 

{-# LANGUAGE Rank2Types      #-}
{-# LANGUAGE GADTs           #-}
{-# LANGUAGE UnicodeSyntax   #-}


module Lens.Explicit (
                     -- * Lenses and other optics
                     -- ** Getters
                       to, Getter, AGetter, (^.)
                     -- ** Setters
                     , sets, Setter, ASetter, (%~), (.~), Setter'
                     -- ** Lenses
                     , lens, Lens, ALens, (%%~), Lens'
                     -- ** Prisms
                     , prism, Prism, APrism, matching, Prism'
                     -- ** Reviews
                     , unto, Review, AReview, re
                     -- ** Isomorphisms
                     , iso, Iso, AnIso, from, under, Iso'
                     -- ** Folds
                     , folded, Fold, AFold, foldMapOf
                     -- ** Traversals
                     , traversed, Traversal, ATraversal, traverseOf, Traversal'
                     -- * Composition
                     -- $composInfo
                     , Cat.id, (Cat..), (&)
                     ) where

import qualified Lens.Explicit.Core as Π–
import Lens.Explicit.Core (OpticC(..))
import Prelude hiding (id, (.))
import Control.Category as Cat
import Data.Function hiding (id, (.))



infixl 8 ^.

(^.) :: 𝑠 -> AGetter 𝑠 π‘Ž -> π‘Ž
s ^. Π–.Equality = s
s ^. OpticC (Π–.Getter f) = f s

to :: (𝑠 -> π‘Ž) -> Getter 𝑠 π‘Ž
to = OpticC . Π–.to

-- | Getters are basically just functions: accessors which can read a field (type @π‘Ž@)
--   of some data structure (type @𝑠@), but not write back anything to the structure.
type Getter 𝑠 π‘Ž = Π–.Getter 𝑠 𝑠 π‘Ž π‘Ž

-- | A getter that may also have additional capabilities, e.g. a 'Lens'.
type AGetter 𝑠 π‘Ž = Π–.AGetter 𝑠 π‘Ž


infixr 4 %~, .~

(%~) :: ASetter 𝑠 𝑑 π‘Ž 𝑏 -> (π‘Ž -> 𝑏) -> 𝑠 -> 𝑑
Π–.Equality %~ m = m
OpticC (Π–.Setter f) %~ m = f m

(.~) :: ASetter 𝑠 𝑑 π‘Ž 𝑏 -> 𝑏 -> 𝑠 -> 𝑑
a .~ b = a %~ const b

sets :: ((π‘Ž -> 𝑏) -> 𝑠 -> 𝑑) -> Setter 𝑠 𝑑 π‘Ž 𝑏
sets = OpticC . Π–.sets

-- | Setters are accessors that can write/manipulate a field (type @π‘Ž@)
--   of a data structure (type @𝑠@), but not retrieve any results.
--
--   The manipulation might result in a type @𝑏@ for the field different from
--   the original @π‘Ž@, in that case, the data structure will likewise change
--   change its type from @𝑠@ to @𝑑@.
type Setter 𝑠 𝑑 π‘Ž 𝑏 = Π–.Setter 𝑠 𝑑 π‘Ž 𝑏

-- | A setter that may also have additional capabilities, e.g. a 'Lens'.
type ASetter 𝑠 𝑑 π‘Ž 𝑏 = Π–.ASetter 𝑠 𝑑 π‘Ž 𝑏

type Setter' 𝑠 π‘Ž = Setter 𝑠 𝑠 π‘Ž π‘Ž


infixr 4 %%~

(%%~) :: Functor 𝑓 => ALens 𝑠 𝑑 π‘Ž 𝑏 -> (π‘Ž -> 𝑓 𝑏) -> 𝑠 -> 𝑓 𝑑
(%%~) Π–.Equality Ο„ s = Ο„ s
(%%~) (OpticC (Π–.Lens f Ο†)) Ο„ s = fmap (Ο† s) . Ο„ $ f s

lens :: (𝑠 -> π‘Ž) -> (𝑠 -> 𝑏 -> 𝑑) -> Lens 𝑠 𝑑 π‘Ž 𝑏
lens f g = OpticC $ Π–.lens f g

-- | Lenses combine the capabilities of 'Getter' and 'Setter' – they have β€œread and
--   write permission”, i.e. you can use them with the '^.' as well as '.~' and '%~'
--   operators.
--
--   This is the standard type of record-field accessor.
type Lens 𝑠 𝑑 π‘Ž 𝑏 = Π–.Lens 𝑠 𝑑 π‘Ž 𝑏

-- | A lens that may also have additional capabilities, e.g. an 'Iso'.
type ALens 𝑠 𝑑 π‘Ž 𝑏 = Π–.ALens 𝑠 𝑑 π‘Ž 𝑏

type Lens' 𝑠 π‘Ž = Lens 𝑠 𝑠 π‘Ž π‘Ž


prism :: (𝑏 -> 𝑑) -> (𝑠 -> Either 𝑑 π‘Ž) -> Prism 𝑠 𝑑 π‘Ž 𝑏
prism f g = OpticC $ Π–.prism f g

matching :: APrism 𝑠 𝑑 π‘Ž 𝑏 -> 𝑠 -> Either 𝑑 π‘Ž
matching Π–.Equality = Right
matching (OpticC (Π–.Prism _ f)) = f

-- | Prisms are the categorical dual of lenses: whilst a lens /focuses/ in on a field
--   of a record structure (i.e. of a product type), a prism /distinguishes/ constructors
--   of an alternative (i.e. of a sum type).
type Prism 𝑠 𝑑 π‘Ž 𝑏 = Π–.Prism 𝑠 𝑑 π‘Ž 𝑏

-- | A prism that may also have additional capabilities, e.g. an 'Iso'.
type APrism 𝑠 𝑑 π‘Ž 𝑏 = Π–.APrism 𝑠 𝑑 π‘Ž 𝑏

type Prism' 𝑠 π‘Ž = Prism 𝑠 𝑠 π‘Ž π‘Ž


unto :: (𝑏 -> 𝑑) -> Review 𝑑 𝑏
unto = OpticC . Π–.unto

re :: Π–.FromGetter c => AReview 𝑑 𝑏 -> Π–.Optic c 𝑑 𝑑 𝑏 𝑏
re Π–.Equality = Π–.Equality
re (OpticC (Π–.Review f)) = OpticC $ Π–.to f

-- | Reviews are basically like constructors in languages without pattern matching:
--   /prisms without read permission/. Because such a constructor is just a function,
--   and getters are functions too, you can also consider a review as a β€œreverse 'Getter'”.
type Review 𝑑 𝑏 = Π–.Review 𝑑 𝑑 𝑏 𝑏

-- | A review that may also have additional capabilities, e.g. a 'Prism'.
type AReview 𝑑 𝑏 = Π–.AReview 𝑑 𝑏


under :: AnIso 𝑠 𝑑 π‘Ž 𝑏 -> (𝑑 -> 𝑠) -> 𝑏 -> π‘Ž
under Π–.Equality g = g
under (OpticC (Π–.Iso f Ο†)) g = f . g . Ο†

from :: AnIso 𝑠 𝑑 π‘Ž 𝑏 -> Iso 𝑏 π‘Ž 𝑑 𝑠
from Π–.Equality = Π–.Equality
from (OpticC (Π–.Iso f Ο†)) = iso Ο† f

iso :: (𝑠 -> π‘Ž) -> (𝑏 -> 𝑑) -> Iso 𝑠 𝑑 π‘Ž 𝑏
iso f g = OpticC $ Π–.iso f g

-- | Isomorphisms are 1-1 mappings. This can be seen as a 'Lens' which focuses on
--   a field that contains the entire information of the data structure, or as a
--   prism that distinguishes the only constructor available.
type Iso 𝑠 𝑑 π‘Ž 𝑏 = Π–.Iso 𝑠 𝑑 π‘Ž 𝑏

-- | An isomorphism that could also have additional capabilities. (This is somewhat
--   theoretical, since isomorphism is already the most powerful relation we describe.)
type AnIso 𝑠 𝑑 π‘Ž 𝑏 = Π–.AnIso 𝑠 𝑑 π‘Ž 𝑏

type Iso' 𝑠 π‘Ž = Iso 𝑠 𝑠 π‘Ž π‘Ž


traverseOf :: Applicative 𝑓 => ATraversal 𝑠 𝑑 π‘Ž 𝑏 -> (π‘Ž -> 𝑓 𝑏) -> 𝑠 -> 𝑓 𝑑
traverseOf Π–.Equality = id
traverseOf (OpticC (Π–.Traversal y)) = y

traversed :: (βˆ€ 𝑓 . Applicative 𝑓 => (π‘Ž -> 𝑓 𝑏) -> 𝑠 -> 𝑓 𝑑) -> Traversal 𝑠 𝑑 π‘Ž 𝑏
traversed f = OpticC (Π–.traversed f)

-- | Traversals can 'Fold' over the fields of a data structure, and additionally
--   reconstruct the structure with modified fields.
type Traversal 𝑠 𝑑 π‘Ž 𝑏 = Π–.Traversal 𝑠 𝑑 π‘Ž 𝑏

-- | A traversal that may also have additional capabilities, e.g. a 'Lens' or 'Prism'.
type ATraversal 𝑠 𝑑 π‘Ž 𝑏 = Π–.ATraversal 𝑠 𝑑 π‘Ž 𝑏

type Traversal' 𝑠 π‘Ž = Traversal 𝑠 𝑠 π‘Ž π‘Ž


foldMapOf :: Monoid π‘Ÿ => AFold 𝑠 π‘Ž -> (π‘Ž -> π‘Ÿ) -> 𝑠 -> π‘Ÿ
foldMapOf Π–.Equality = id
foldMapOf (OpticC (Π–.Fold y)) = y

folded :: Foldable 𝑓 => Fold (𝑓 π‘Ž) π‘Ž
folded = OpticC $ Π–.folded

-- | Folds access fields that may occur multiple times in the data structure,
--   or not at all, such as the elements of a list. Like 'Getter', they don't
--   have β€œwrite permission”.
type Fold 𝑠 π‘Ž = Π–.Fold 𝑠 𝑠 π‘Ž 𝑠

-- | A fold that may also have additional capabilities, e.g. a 'Getter' or 'Traversal'.
type AFold 𝑠 π‘Ž = Π–.AFold 𝑠 𝑠 π‘Ž 𝑠

-- $composInfo
-- Optics compose β€œOO style”, from left to right. For example, given
-- 
-- @
-- data Foo = Foo Int String
-- foostr :: 'Lens'' Foo String
-- data Bar = Bar Foo Bool
-- barfoo :: 'Lens'' Bar Foo
-- 
-- hideout :: bar
-- hideout = Bar (Foo 7 "I'm here!") True
-- @
--
-- you can use
--
-- @
--    hideout '^.' barfoo'.'foostr
-- @
--
-- to look up the @"I'm here!"@ string.
-- 
-- Optics of different power can directly be composed with each other, for instance,
-- in the example above it would have also been sufficient if
--
-- @
-- barfoo :: 'Getter' Bar Foo
-- @