vinyl-0.9.0: Data/Vinyl/Lens.hs
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
-- | Lenses into record fields.
module Data.Vinyl.Lens
( RecElem(..)
, rget, rput, rput', rlens, rlens'
, RElem
, RecSubset(..)
, rsubset, rcast, rreplace
, RSubset
, REquivalent
, type (∈)
, type (⊆)
, type (≅)
, type (<:)
, type (:~:)
) where
import Data.Kind (Constraint)
import Data.Vinyl.Core
import Data.Vinyl.Functor
import Data.Vinyl.TypeLevel
-- | The presence of a field in a record is witnessed by a lens into
-- its value. The fifth parameter to 'RecElem', @i@, is there to help
-- the constraint solver realize that this is a decidable predicate
-- with respect to the judgemental equality in @k@.
class i ~ RIndex r rs => RecElem record (r :: k) (r' :: k)
(rs :: [k]) (rs' :: [k])
(i :: Nat) | r r' rs i -> rs' where
-- | An opportunity for instances to generate constraints based on
-- the functor parameter of records passed to class methods.
type RecElemFCtx record (f :: k -> *) :: Constraint
type RecElemFCtx record f = ()
-- | We can get a lens for getting and setting the value of a field which is
-- in a record. As a convenience, we take a proxy argument to fix the
-- particular field being viewed. These lenses are compatible with the @lens@
-- library. Morally:
--
-- > rlensC :: Lens' (Rec f rs) (Rec f rs') (f r) (f r')
rlensC
:: (Functor g, RecElemFCtx record f)
=> (f r -> g (f r'))
-> record f rs
-> g (record f rs')
-- | For Vinyl users who are not using the @lens@ package, we provide a getter.
rgetC
:: (RecElemFCtx record f, r ~ r')
=> record f rs
-> f r
-- | For Vinyl users who are not using the @lens@ package, we also provide a
-- setter. In general, it will be unambiguous what field is being written to,
-- and so we do not take a proxy argument here.
rputC
:: RecElemFCtx record f
=> f r'
-> record f rs
-> record f rs'
-- | 'RecElem' for classic vinyl 'Rec' types.
type RElem x rs = RecElem Rec x x rs rs
-- This is an internal convenience stolen from the @lens@ library.
lens
:: Functor f
=> (s -> a)
-> (s -> b -> t)
-> (a -> f b)
-> s
-> f t
lens sa sbt afb s = fmap (sbt s) $ afb (sa s)
{-# INLINE lens #-}
instance RecElem Rec r r' (r ': rs) (r' ': rs) 'Z where
rlensC f (x :& xs) = fmap (:& xs) (f x)
{-# INLINE rlensC #-}
rgetC = getConst . rlensC Const
{-# INLINE rgetC #-}
rputC y = getIdentity . rlensC @_ @r (\_ -> Identity y)
{-# INLINE rputC #-}
instance (RIndex r (s ': rs) ~ 'S i, RecElem Rec r r' rs rs' i)
=> RecElem Rec r r' (s ': rs) (s ': rs') ('S i) where
rlensC f (x :& xs) = fmap (x :&) (rlensC f xs)
{-# INLINE rlensC #-}
rgetC = getConst . rlensC @_ @r @r' Const
{-# INLINE rgetC #-}
rputC y = getIdentity . rlensC @_ @r (\_ -> Identity y)
{-# INLINE rputC #-}
-- | The 'rgetC' field getter with the type arguments re-ordered for
-- more convenient usage with @TypeApplications@.
rget :: forall r rs f record.
(RecElem record r r rs rs (RIndex r rs), RecElemFCtx record f)
=> record f rs -> f r
rget = rgetC
-- | The type-changing field setter 'rputC' with the type arguments
-- re-ordered for more convenient usage with @TypeApplications@.
rput' :: forall r r' rs rs' record f. (RecElem record r r' rs rs' (RIndex r rs), RecElemFCtx record f)
=> f r' -> record f rs -> record f rs'
rput' = rputC @_ @r @r'
-- | Type-preserving field setter. This type is simpler to work with
-- than that of 'rput''.
rput :: forall r rs record f. (RecElem record r r rs rs (RIndex r rs), RecElemFCtx record f)
=> f r -> record f rs -> record f rs
rput = rput' @r
-- | Type-changing field lens 'rlensC' with the type arguments
-- re-ordered for more convenient usage with @TypeApplications@.
rlens' :: forall r r' record rs rs' f g.
(RecElem record r r' rs rs' (RIndex r rs), RecElemFCtx record f, Functor g)
=> (f r -> g (f r')) -> record f rs -> g (record f rs')
rlens' = rlensC
-- | Type-preserving field lens. This type is simpler to work with
-- than that of 'rlens''.
rlens :: forall r record rs f g.
(RecElem record r r rs rs (RIndex r rs), RecElemFCtx record f, Functor g)
=> (f r -> g (f r)) -> record f rs -> g (record f rs)
rlens = rlensC
-- | If one field set is a subset another, then a lens of from the latter's
-- record to the former's is evident. That is, we can either cast a larger
-- record to a smaller one, or we may replace the values in a slice of a
-- record.
class is ~ RImage rs ss => RecSubset record (rs :: [k]) (ss :: [k]) is where
-- | An opportunity for instances to generate constraints based on
-- the functor parameter of records passed to class methods.
type RecSubsetFCtx record (f :: k -> *) :: Constraint
type RecSubsetFCtx record f = ()
-- | This is a lens into a slice of the larger record. Morally, we have:
--
-- > rsubset :: Lens' (Rec f ss) (Rec f rs)
rsubsetC
:: (Functor g, RecSubsetFCtx record f)
=> (record f rs -> g (record f rs))
-> record f ss
-> g (record f ss)
-- | The getter of the 'rsubset' lens is 'rcast', which takes a larger record
-- to a smaller one by forgetting fields.
rcastC
:: RecSubsetFCtx record f
=> record f ss
-> record f rs
rcastC = getConst . rsubsetC Const
{-# INLINE rcastC #-}
-- | The setter of the 'rsubset' lens is 'rreplace', which allows a slice of
-- a record to be replaced with different values.
rreplaceC
:: RecSubsetFCtx record f
=> record f rs
-> record f ss
-> record f ss
rreplaceC rs = getIdentity . rsubsetC (\_ -> Identity rs)
{-# INLINE rreplaceC #-}
-- | A lens into a slice of the larger record. This is 'rsubsetC' with
-- the type arguments reordered for more convenient usage with
-- @TypeApplications@.
rsubset :: forall rs ss f g record is.
(RecSubset record (rs :: [k]) (ss :: [k]) is,
Functor g, RecSubsetFCtx record f)
=> (record f rs -> g (record f rs)) -> record f ss -> g (record f ss)
rsubset = rsubsetC
-- | Takes a larger record to a smaller one by forgetting fields. This
-- is 'rcastC' with the type arguments reordered for more convenient
-- usage with @TypeApplications@.
rcast :: forall rs ss f record is.
(RecSubset record rs ss is, RecSubsetFCtx record f)
=> record f ss -> record f rs
rcast = rcastC
-- | Allows a slice of a record to be replaced with different
-- values. This is 'rreplaceC' with the type arguments reordered for
-- more convenient usage with @TypeApplications@.
rreplace :: forall rs ss f record is.
(RecSubset record rs ss is, RecSubsetFCtx record f)
=> record f rs -> record f ss -> record f ss
rreplace = rreplaceC
type RSubset = RecSubset Rec
instance RecSubset Rec '[] ss '[] where
rsubsetC = lens (const RNil) const
instance (RElem r ss i , RSubset rs ss is) => RecSubset Rec (r ': rs) ss (i ': is) where
rsubsetC = lens (\ss -> rget ss :& rcastC ss) set
where
set :: Rec f ss -> Rec f (r ': rs) -> Rec f ss
set ss (r :& rs) = rput r $ rreplaceC rs ss
-- | Two record types are equivalent when they are subtypes of each other.
type REquivalent rs ss is js = (RSubset rs ss is, RSubset ss rs js)
-- | A shorthand for 'RElem' which supplies its index.
type r ∈ rs = RElem r rs (RIndex r rs)
-- | A shorthand for 'RSubset' which supplies its image.
type rs ⊆ ss = RSubset rs ss (RImage rs ss)
-- | A shorthand for 'REquivalent' which supplies its images.
type rs ≅ ss = REquivalent rs ss (RImage rs ss) (RImage ss rs)
-- | A non-unicode equivalent of @(⊆)@.
type rs <: ss = rs ⊆ ss
-- | A non-unicode equivalent of @(≅)@.
type rs :~: ss = rs ≅ ss