ten-lens-0.1.0.0: src/Data/Ten/Lens.hs
-- Copyright 2018-2021 Google LLC
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
-- | Provides lenses and related functionality for the "ten" package.
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
module Data.Ten.Lens
( rep10, field10, ixRep10, ap10, comp
, _Field10, _Field10', (!=), (!=?), fragmented
) where
import Data.Functor.Contravariant (contramap)
import Data.Type.Equality ((:~:)(..))
import Control.Lens (Getting, Prism', Iso, iso, view)
import Control.Lens.Setter ((%~), Setter, ASetter, setting)
import Data.GADT.Compare (GEq(..))
import Data.Profunctor (dimap, right')
import Data.Ten.Ap (Ap10(..))
import Data.Ten.Sigma ((:**)(..), OpCostar, lmapFragment)
import Data.Ten.Representable (Representable10(..), rep10', field10')
import Data.Ten.Update (ixRep10)
import Data.Ten ((:.:)(..))
-- | Turn a record field lens into a 'Rep10'.
--
-- Since 'tabulate10' can give us a record of 'Rep10's, all we have to do to
-- convert a lens into a 'Rep10' is use 'view' to extract the desired 'Rep10'.
rep10
:: Representable10 f
=> Getting (Rep10 f a) (f (Rep10 f)) (Rep10 f a) -> Rep10 f a
rep10 l = rep10' (view l)
-- | Convert a lens targeting Ap10 to a Rep10.
field10
:: Representable10 rec
=> Getting (Ap10 a (Rep10 rec)) (rec (Rep10 rec)) (Ap10 a (Rep10 rec))
-> Rep10 rec a
field10 l = field10' (view l)
-- | An 'Iso' between an @Ap10 a m@ wrapper and its contained @m a@.
ap10 :: Iso (Ap10 s fs) (Ap10 t ft) (fs s) (ft t)
ap10 = iso unAp10 Ap10
-- | An 'Iso' between a @(m :.: n) a@ wrapper and its contained @m (n a)@.
comp :: Iso ((m :.: n) a) ((k :.: l) b) (m (n a)) (k (l b))
comp = iso unComp1 Comp1
-- | A 'Control.Lens.Prism' from a ':**" to a particular field.
--
-- _Field10 k f (k := m) === (k :=) <$> f m
-- _Field10 k' f (k := m) === k := m | k' /= k
-- _Field10 k # m === k := m
_Field10
:: GEq k
=> k a -> Prism' (k :** m) (m a)
_Field10 k = dimap toE fromE . right'
where
toE frag@(k' :** m) = case geq k k' of
Just Refl -> Right m
Nothing -> Left frag
fromE = either pure (fmap (k :**))
-- | '_Field10' taking the field lens rather than the 'Rep10'.
_Field10'
:: forall rec a m
. (GEq (Rep10 rec), Representable10 rec)
=> (forall n. Getting (Ap10 a n) (rec n) (Ap10 a n))
-> Prism' (Rep10 rec :** m) (m a)
_Field10' l = _Field10 @(Rep10 rec) (field10 l)
infixr 5 !=
-- | Shortcut to construct a (':**') from a 'Control.Lens.Getter'.
--
-- Note that this assumes the fields are ultimately wrapped in 'Ap10'. If a
-- particular field doesn't have 'Ap10' (which can only arise from a
-- manually-written 'Representable10' instance), just pretend it does by adding
-- @from ap10@ to the lens.
(!=)
:: Representable10 rec
=> (forall m. Getting (Ap10 a m) (rec m) (Ap10 a m))
-> f a -> Rep10 rec :** f
l != x = field10 l :** x
infixr 5 !=?
-- | Shortcut to construct a (':**') using 'pure' for the value.
(!=?)
:: (Representable10 rec, Applicative f)
=> (forall m. Getting (Ap10 a m) (rec m) (Ap10 a m))
-> a -> Rep10 rec :** f
l !=? x = l != pure x
-- | Lifts a 'Setter' to work underneath (':**').
--
-- This means if you know how to change the type of a whole record, you can use
-- this to change the type of a (':**').
--
-- Example usage:
--
-- data MyRecord a m = MyRecord { _mrA :: Ap10 a m, _mrInt :: Ap10 Int m }
-- mrA :: Lens' (MyRecord a m) (MyRecord b m) (Ap10 m a) (Ap10 m b)
--
-- example
-- :: Rep10 (MyRecord Int) :** Identity
-- -> Rep10 (MyRecord String) :** Identity
-- example = fragmented (mrA.ap10) %~ show
fragmented
:: ( Functor m, Representable10 recA, Representable10 recB
, f ~ OpCostar m (Rep10 recB :** m)
)
=> ASetter (recB f) (recA f) (f b) (f a)
-> Setter (Rep10 recA :** m) (Rep10 recB :** m) a b
fragmented l = setting $ \f -> lmapFragment (l %~ contramap f)