packages feed

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)