The most successful representation of lenses must be the Van Laarhoven encoding
of lenses in Haskell. This representation makes use of advanced features of haskell
like impredicativity of a functor instance to represent a large family of lens-like datastructures. In our dependently-typed world, this
is nothing special, and we can achieve the same type signature easily:
First we have a generic constructor for VanLaarhoven-style lenses.
0 RefF : ((Type -> Type) -> Type) -> Type -> Type -> TypeRefF constraint a b = (0 f : Type -> Type) -> constraint f -> (b -> f b) -> (a -> f a)
the constraint argument is meant to be an interface such as Functor or
Monad that will ensure some properties on the the functor f that is
given extistentially in the signature. For example lenses in the
VHL representation are defined as ∀ f. Functor f => (b -> f b) -> a -> f a
We represent this as RefF Functor.
namespace VLH public export 0 Lens : Type -> Type -> Type Lens = RefF Functor public export 0 Lens' : Type -> Type -> Type Lens' a b = (f : Endo Set) -> (b -> f.mapObj b) -> a -> f.mapObj a
We do the same for the definition of affine traversals, that make use
of Pointed and traversals that make use of Applicative
public export 0 Affine : Type -> Type -> Type Affine = RefF Pointed public export 0 Traversal : Type -> Type -> Type Traversal = RefF Applicative
Those definitions seem to have nothing in common with the definitions of
lenses, and traversals written in their direct style:
namespace Direct public export Lens : (a, b : Type) -> Type Lens a b = a -> (b * (b -> a)) public export Affine : (a, b : Type) -> Type Affine a b = a -> CoStore b a public export Traversal : (a, b : Type) -> Type Traversal a b = a -> Σ (n : Nat) | (Vect n b * (Vect n b -> a))
But in fact they are isomorphic:
namespace Lens directToVLH : {a, b : _} -> Direct.Lens a b -> VLH.Lens' a b directToVLH ls fn c x = fn.mapHom ? ? (ls x).π2 (c (ls x).π1) vlhToDirect : {a, b : _} -> VLH.Lens' a b -> Direct.Lens a b vlhToDirect f x = f (StoreFunctor b) (\bv => bv && id) x 0 toFrom : (lens : Direct.Lens a b) -> vlhToDirect (directToVLH lens) ≡ lens toFrom lens = funExt $ \x => prodUniq _ -- use yoneda 0 fromTo : (lens : VLH.Lens' a b) -> directToVLH (vlhToDirect lens) ≡ lens fromTo ls = funExtDep $ \fn => funExt $ \mn => funExt $ \x => ?ido lensIso : {a, b : _} -> Direct.Lens a b ≅ VLH.Lens' a b lensIso = MkIso directToVLH vlhToDirect fromTo toFrom
namespace Affine directToVLH : {a, b : _} -> Direct.Affine a b -> VLH.Affine a b directToVLH ls fn c f y = case ls y of (Skip z) => point z (Cont z z2) => map z2 (f z) vlhToDirect : {a, b : _} -> VLH.Affine a b -> Direct.Affine a b vlhToDirect f x = f (CoStore b) %search (\y => point y) x 0 toFrom : (lens : Direct.Affine a b) -> (x : a) -> vlhToDirect (directToVLH lens) x ≡ lens x toFrom lens x with (lens x) toFrom lens x | (Skip y) = Refl toFrom lens x | (Cont y f) = let ff = VanLaarhoven.presFn {f = CoStore b} f in ?udia_rhsa_1 -- use yoneda 0 fromTo : (lens : VLH.Affine a b) -> directToVLH (vlhToDirect lens) ≡ lens fromTo ls = ?Yonedahui lensIso : {a, b : _} -> Direct.Affine a b ≅ VLH.Affine a b lensIso = MkIso directToVLH vlhToDirect fromTo (\x => funExt (toFrom x))
namespace Traversal directToVLH : {a, b : _} -> Direct.Traversal a b -> VLH.Traversal a b directToVLH ls fn c x = ?aha vlhToDirect : {a, b : _} -> VLH.Traversal a b -> Direct.Traversal a b vlhToDirect f x = ?hui 0 toFrom : (lens : Direct.Traversal a b) -> vlhToDirect (directToVLH lens) ≡ lens toFrom lens = ?udia -- use yoneda 0 fromTo : (lens : VLH.Traversal a b) -> directToVLH (vlhToDirect lens) ≡ lens fromTo ls = ?Yonedahui lensIso : {a, b : _} -> Direct.Traversal a b ≅ VLH.Traversal a b lensIso = MkIso directToVLH vlhToDirect fromTo toFrom
We present here another way to conceptualise traditional lenses, and that is
as monads on containers
FromM : (Container -> Container) -> Type -> Type -> TypeFromM c a b = a -> Ex (c (b :- b)) anamespace Dependent public export Lens, Affine, Traversal : (a, b : Type) -> Type Lens = FromM id Affine = FromM Any.Maybe Traversal = FromM All.List
Each of our existing lens is defined as the reader monad on a of an indexed functor
F b.
Lens = a -> F b a where F b a = b * (b -> a)
Affine = a -> F b a where F b a = a + b * (b -> a)
Traversal = a -> F b a where F b a = Σ (n : Nat) | Vect n b * (Vect n b -> a)
Since the reader part a -> is always the same, it’s enough to find the following
isomorphisms:
b * (b -> a) ~ Ex (b :- b) a
a + (b * (b -> a)) ~ Ex (All.Maybe (b :- b)) a
Σ (n : Nat) | Vect n b * (Vect n b -> a) ~ Ex (All.List (b :- b)) a
Proposition
The Store comonad Storeba=b∗b→a is isomorphic to
Ex(b,b)a
StoreComonadIso : (b * (b -> a)) ≅ Ex (b :- b) aStoreComonadIso = MkIso ?StoreComonadIso_rhs_0 ?StoreComonadIso_rhs_1 ?StoreComonadIso_rhs_2 ?StoreComonadIso_rhs_3
Proposition
The Pointed functor Affba=a+b∗(b→a is isomorphic to
ExMaybe(b,b)a
The applicative functor Σ (n : Nat) | Vect n a * (Vect n a -> b) is isomorphic
to Ex(List(b,b))a
namespace Traversal Tr : (a, b : Type) -> Type Tr a b = Σ (n : Nat) | Vect n b * (Vect n b -> a) vectToList : Vect n a -> List a vectToList [] = [] vectToList (x :: xs) = x :: vectToList xs AllToListVect : {xs : Vect len b} -> All (\_ => b) (vectToList xs) -> Vect len b AllToListVect {xs = [] } [] = [] AllToListVect {xs = x :: xs } (y :: ys) = y :: AllToListVect ys toEx : Tr a b -> Ex (All.List (b :- b)) a toEx (len ## (ls && ks)) = MkEx (vectToList ls) (ks . AllToListVect) listAllToVect : (x1 : List b) -> Vect (length x1) b listAllToVect [] = [] listAllToVect (x :: xs) = x :: listAllToVect xs vectToAll : (x1 : List b) -> Vect (length x1) b -> All (\x => b) x1 vectToAll [] x1 = [] vectToAll (_ :: _) (x :: xs) = x :: vectToAll _ xs fromEx : Ex (All.List (b :- b)) a -> Tr a b fromEx (MkEx x1 x2) = length x1 ## (listAllToVect x1 && x2 . vectToAll x1) vectReverse : (xs : List a) -> vectToList (listAllToVect xs) ≡ xs vectReverse [] = Refl vectReverse (x :: xs) = cong (x ::) (vectReverse xs) transportConsAll : {b : Type} -> {p : b -> Type} -> {x : b} -> {v : p x} -> {ls, ks : List b} -> {vs : Quantifiers.All.All p ls} -> {ys : Quantifiers.All.All p ks} -> {prf : ls === ks} -> transport (All p) (cong (Prelude.(::) x) prf) (v :: vs) ≡ v :: transport (Quantifiers.All.All p) prf vs transportConsAll {prf = Refl} = trans (applyTransport ? ?) (congDep (All.(::) v) (sym $ applyTransport ? ?)) vectReverse² : (x1 : List b) -> (v : All (\x => b) (vectToList (listAllToVect x1))) -> (vectToAll x1 (listAllToVect x1)) `ListAllEq` (replace {p = (All (\x => b))} (vectReverse x1) v) vectReverse² x1 v = ?vectReverse²_rhs-- vectReverse¹ : {b : Type} ->-- (x1 : List b) ->-- (x : All (\x => b) (vectToList (listAllToVect x1)))->-- vectToAll x1 (AllToListVect x) === x 0 fromToEx : (x : Ex (All.List (b :- b)) a) -> toEx (fromEx x) = x fromToEx (MkEx x1 x2) = exEqToEq $ MkExEq (vectReverse x1) (\x => ?adh) 0 toFromEx : (x : Tr a b) -> fromEx (toEx x) `SigEq` x toFromEx (Z ## ([] && x3)) = MkSigEq Refl (eqToIxEq $ cong ([] &&) (funExt $ \[] => Refl)) toFromEx (S x ## (x1 :: x2 && x3)) = let MkSigEq e1 e2 = assert_total $ toFromEx (x ## (x2 && (x3 . (x1 ::)))) in MkSigEq (cong S e1) ?toFromEx_rhs TraversalListIso : Tr a b ≅ Ex (All.List (b :- b)) a TraversalListIso = MkIso toEx fromEx fromToEx (\x => sigEqToEq $ toFromEx x)
Can this transformation teach us about new or existing lenses? Next we apply this
transformation, but in reverse. From a lens (a :- a) =%> Star (b :- b) what
do we obtain in the world of non-dependent lenses?
The first step is to decompose our lens
(x : a) -> Σ (y : StarShp (b :- b)) | StarPos (b :- b) y -> a
Then we notice that removing the dependency on StarFw results in the free monad on the store comonad
data StarSimple a = Done a | More a (a -> StarFw a)
`(x : a) → Σ (y : Free Store) | StarBw (b :- b) y → a
Removing the dependency from StarBw results in a list of responses b of the same size as the depth of y
(x : a) -> Free Store * (List b -> a)
This looks exactly like the free monad on the store comonad
data Free : (Type -> Type) -> Type -> Type where
Done : a -> Free m a
More : m (Free m a) -> Free m a
namespace Lens directToDependent : {a, b : _} -> Direct.Lens a b -> Dependent.Lens a b directToDependent ls x = uncurry MkEx (ls x) dependentToDirect : {a, b : _} -> Dependent.Lens a b -> Direct.Lens a b dependentToDirect f x = (f x).ex1 && (f x).ex2 0 toFrom' : (lens : Direct.Lens a b) -> dependentToDirect (directToDependent lens) ≡ lens toFrom' lens = funExt $ \x => ?udia -- use yoneda 0 fromTo' : (lens : Dependent.Lens a b) -> directToDependent (dependentToDirect lens) ≡ lens fromTo' ls = ?Yonedahui lensIso' : {a, b : _} -> Direct.Lens a b ≅ Dependent.Lens a b lensIso' = MkIso directToDependent dependentToDirect fromTo' toFrom'
namespace Affine directToDependent : {a, b : _} -> Direct.Affine a b -> Dependent.Affine a b directToDependent ls = ?aha dependentToDirect : {a, b : _} -> Dependent.Affine a b -> Direct.Affine a b dependentToDirect f x = ?hui 0 toFrom' : (lens : Direct.Affine a b) -> dependentToDirect (directToDependent lens) ≡ lens toFrom' lens = ?udia -- use yoneda 0 fromTo' : (lens : Dependent.Affine a b) -> directToDependent (dependentToDirect lens) ≡ lens fromTo' ls = ?Yonedahuil lensIso' : {a, b : _} -> Direct.Affine a b ≅ Dependent.Affine a b lensIso' = MkIso directToDependent dependentToDirect fromTo' toFrom'
namespace Traversal directToDependent : {a, b : _} -> Direct.Traversal a b -> Dependent.Traversal a b directToDependent ls = ?aha2 dependentToDirect : {a, b : _} -> Dependent.Traversal a b -> Direct.Traversal a b dependentToDirect f x = ?hui2j 0 toFrom' : (lens : Direct.Traversal a b) -> dependentToDirect (directToDependent lens) ≡ lens toFrom' lens = ?udiak -- use yoneda 0 fromTo' : (lens : Dependent.Traversal a b) -> directToDependent (dependentToDirect lens) ≡ lens fromTo' ls = ?Yonedahuik lensIso' : {a, b : _} -> Direct.Traversal a b ≅ Dependent.Traversal a b lensIso' = MkIso directToDependent dependentToDirect fromTo' toFrom'
We introduce a new lens-like structure, the monadic traversal where
the constraint is a monad.
0 MonadicTraversal : Type -> Type -> TypeMonadicTraversal = RefF Monad
Correspondances between VLH lenses and dependent lenses
0 Traversal : (a, b, c, d : Type) -> TypeTraversal a b c d = forall f. (app : Applicative f) => (c -> f d) -> a -> f bVectToList : Vect n a -> List aVectToList [] = []VectToList (x :: xs) = x :: VectToList xsAllToListVect : {xs : Vect len b} -> All (\_ => b) (VectToList xs) -> Vect len bAllToListVect {xs} _ = xstraversalToDLens : TravF b a -> a :- a =&> All.List (b :- b)traversalToDLens trav = !! \x => let MkCartStore len xs ks = trav x (CartStore b) %search pure in VectToList xs ## ks . AllToListVect {xs}list2ConstAll : (xs : List a) -> All (\_ => a) xslist2ConstAll [] = []list2ConstAll (x :: xs) = x :: list2ConstAll xsdlensToTraversal : a :- a =&> All.List (b :- b) -> TravF b adlensToTraversal (k) y func app g = case k.fn y of ([] ## p2) => pure (p2 []) ((x :: xs) ## p2) => let gv = g x pp = p2 <$> ((::) <$> gv <*> pure (list2ConstAll xs)) rr : func _ = dlensToTraversal k <$> pp -- ss : func (Applicative func -> (b -> func b) -> func a) = rr <*> pure func -- failure due to lack of parametricity in pp
interface Functor f => Pointed f where point : a -> f a0 AffineTraversal : (a, b, c, d : Type) -> TypeAffineTraversal a b c d = forall f. Pointed f => (c -> f d) -> a -> f b
0 MonadicTraversal : (a, b, s, t : Type) -> TypeMonadicTraversal a b s t = (0 f : Type -> Type) -> (m : Monad f) => (a -> f s) -> t -> f batob : StarShp (a :- a) -> StarShp (b :- b)atob Done = Doneatob (More x f) = More ?db ?atob_rhs_1MonadicToDLens : forall a, b. MonF b a -> (a :- a) =&> Star (b :- b)MonadicToDLens tr = !! \x => let cs : CompStore b a cs = tr {f = CompStore b} x pure in cs.ls ## cs.ksDLensToMonadoc : a :- a =&> Star (b :- b) -> MonF b aDLensToMonadoc (!! k) x g = elimSig {d = \_ => f a} (k x) $ \x1, x2, p1, p2 => elimStar {d = \_ => f a} x1 ((\pq => pure $ x2 $ rewrite pq in StarU)) (\p1, p2, pq => (g p1) >>= \gv => let newX = (x2 (rewrite pq in StarM gv (mapStarToPos (p2 gv)))) in ?huia ) {-0DLensMonadDLens : (dlens : a :- a =&> Star (b :- b)) -> (x : a) -> (MonadicToDLens (DLensToMonadoc dlens)).fn x `SigEqS` dlens.fn xDLensMonadDLens (!! fn) x with (fn x) DLensMonadDLens (!! fn) x | (Done ## p2) = MkSigEqS Refl (funExt $ \StarU => ?aa_rhsb_02) DLensMonadDLens (!! fn) x | (More p p1 ## p2) = MkSigEqS ?bdbd ?aa_rhsb_0 {-
Dependent VanLaarhoven lenses
Something we cannot do in Haskell easily is give this a dependent version, and we do so
by turning each pair of input-outputs into a container:
0 DVLH : (a, b : Container) -> Type
DVLH a b =
forall f. Functor f =>
((x : a.req) -> f (a.res x)) -> ((x : b.req) -> f (b.res x))
To make things a bit more clear when proving properties of dependent van-laarhoven
lenses, I am going to use a version where all the arguments are explicit:
0 DVLH : (a, b : Container) -> TypeDVLH a b = (0 f : Type -> Type) -> Functor f -> ((x : a.req) -> f (a.res x)) -> ((x : b.req) -> f (b.res x))
As expected we can compose those dependen van-laarhoven lenses:
compose : DVLH a b -> DVLH b c -> DVLH a ccompose g1 g2 f c x = g2 f c (g1 f c x)
Just like their haskell counterpart, composition is just function composition.
Thanks to this fact, we know that dependent van-laarhoven lenses form a category
since function composition is associative and respects identities.