app-lens (empty) → 0.1.0.0
raw patch · 12 files changed
+1286/−0 lines, 12 filesdep +app-lensdep +basedep +containerssetup-changed
Dependencies added: app-lens, base, containers, criterion, deepseq, lens, mtl
Files
- Bench/Compositions.hs +33/−0
- Bench/Eval.hs +48/−0
- Control/LensFunction.hs +220/−0
- Control/LensFunction/Core.hs +419/−0
- Control/LensFunction/Exception.hs +76/−0
- Control/LensFunction/Internal.hs +102/−0
- Control/LensFunction/InternalL.hs +59/−0
- Control/LensFunction/Util.hs +31/−0
- Examples/Evaluator.hs +68/−0
- LICENSE +30/−0
- Setup.hs +2/−0
- app-lens.cabal +198/−0
+ Bench/Compositions.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}++import Control.LensFunction+import Examples.Evaluator hiding (incL)++import Criterion.Main+import Control.Lens++test n = unlift (\x -> iterate (lift incL) x !! n)+ where+ incL :: Lens' Int Int+ incL = lens' $ \s -> (s + 1, (\v -> v - 1))++test2 = unliftT (\(x:xs) -> foldl (lift2 addL) x xs)+ where+ addL :: Lens' (Int, Int) Int + addL = lens' $ \(a,b) -> (a + b, \v -> (v - b, b))+ ++++put l s v = set l v s++main = defaultMain [+ bgroup "composition" [ bench "U10000000" $ nf (put (test 10000000) 0) 0+ , bench "U20000000" $ nf (put (test 20000000) 0) 0+ , bench "B5000" $ nf (put test2 [0..5000]) 0+ , bench "B10000" $ nf (put test2 [0..10000]) 0+ ]+ ]+
+ Bench/Eval.hs view
@@ -0,0 +1,48 @@+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}++import Control.LensFunction+import Examples.Evaluator hiding (incL)++import Criterion.Main++import Control.DeepSeq+import Control.Lens+++instance NFData a => NFData (Env a) where+ rnf (Env xs) = rnf xs++instance NFData a => NFData (Val a) where+ rnf (VNum a) = rnf a+ rnf (VFun s e env) = rnf s `seq` rnf e `seq` rnf env++instance NFData Exp where+ rnf (ENum i) = rnf i+ rnf (EInc e) = rnf e+ rnf (EFun s e) = rnf s `seq` rnf e+ rnf (EApp e1 e2) = rnf e1 `seq` rnf e2+ rnf (EVar e) = rnf e+++expr1 = twice @@ twice @@ (twice @@ twice @@ twice @@ (twice @@ twice @@ twice @@ twice @@ inc)) @@ x + where+ twice = EFun "f" $ EFun "x" $+ EVar "f"@@ (EVar "f" @@ EVar "x")+ inc = EFun "x" (EInc (EVar "x"))+ x = EVar "x"++incL :: Lens' Int Int+incL = lens' $ \s -> (s + 1, (\v -> v - 1))++put l s v = set l v s ++main = defaultMain [+ bgroup "evalL" [ bench "E0" $ nf (put (evalL expr) env0) (VNum 0)+ , bench "E1000" $ nf (put (evalL expr) (envn 1000)) (VNum 0)+ , bench "E2000" $ nf (put (evalL expr) (envn 2000)) (VNum 0)+ , bench "E3000" $ nf (put (evalL expr) (envn 3000)) (VNum 0)+ ]+ ]+
+ Control/LensFunction.hs view
@@ -0,0 +1,220 @@+{-# LANGUAGE RankNTypes #-}++-- Required for sequenceL, if we use var Laarhoven repl.+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE Safe #-}++{-|+This module provides an "applicative" (functional) way of composing+lenses through the data type 'L'. For example, this module enables us+to define a "lens" version of 'unlines' as follows.++@+unlinesF :: [L s String] -> L s String+unlinesF [] = new ""+unlinesF (x:xs) = catLineF x (unlinesF xs)+ where catLineF = lift2 catLineL++catLineL :: Lens' (String, String) String+catLineL = ...+@++To make a lens from such "lens functions", one can use unlifting+functions ('unlift', 'unlift2', 'unliftT') as follows.++@+unlinesL :: Lens' [String] String+unlinesL = unliftT unlinesF+@++The obtained lens works as expected (here 'Control.Lens.^.', 'Control.Lens.&'+and 'Control.Lens..~' are taken from "Control.Lens").++>>> ["banana", "orange", "apple"] ^. unlinesL+"banana\norange\napple\n"+>>> ["banana", "orange", "apple"] & unlinesL .~ "Banana\nOrange\nApple\n"+["Banana","Orange","Apple"]++One can understand that @L s a@ is an updatable @a@. +The type @[L s String] -> L s String@ of @unlinesF@ tells us that+we can update only the list elements.+Actually, insertion or deletion of lines to the view will fail, as below.++>>> ["banana", "orange", "apple"] & unlinesL .~ "Banana\nOrange\nApple"+*** Exception: ...+>>> ["banana", "orange", "apple"] & unlinesL .~ "Banana\nOrange\nApple\n\n"+*** Exception: ...++If you want to reflect insertions and deletions, one have to write a+function of type @L s [String] -> L s String@, which says that the+list structure itself would be updatable. To write a function of this+type, 'liftC' and 'liftC2' functions would be sometimes useful.++@+unlinesF' :: L s [String] -> L s String+unlinesF' = liftC (foldWithDefault "" "\n") (lift catLineL')++catLineL' :: Lens' (Either () (String,String)) String+catLineL' = ...++foldWithDefault :: a -> (Lens' (Either () (a,b)) b) -> Lens' [a] b+foldWithDefault d f = ...+@+++++-}++module Control.LensFunction+ (++ -- * Core Datatype+ L() -- abstract++ -- * Other constructors for @Lens'@+ , lens'+ -- * Functions handling pairs and containers+ , unit, pair, list, sequenceL++ -- * Lifting Functions + , new, lift, lift2, liftT+ , liftLens, liftLens'+ -- * Unlifting Functions+ , unlift, unlift2, unliftT++ -- * Functions for Handling Observations+ -- ** Core Monad+ , R() -- abstract+ -- ** Lifting Observations+ , observe+ , liftO, liftO2+ -- ** Unlifting Functions + , unliftM, unliftM2, unliftMT+ -- * Lifting Functions for Combinators + , liftC, liftC2 + , module Control.LensFunction.Exception+ ) where++import Control.LensFunction.Core+import Control.LensFunction.Util+import Control.LensFunction.Exception++import Data.Traversable (Traversable)++import Control.Exception++import qualified Control.Lens as L +---------------------------------------------------------++mName :: String+mName = "Control.LensFunction"++{- | +The nullary version of a lifting function. Since there is no source,+every view generated by 'new' is not updatable.++The function will throw 'ConstantUpdateException', if its view is+updated. +-}+new :: Eq a => a -> L s a+new a = lift (lens' $ const (a, check a)) unit+ where+ check x x' = if x == x' then+ ()+ else+ throw (ConstantUpdateException $ mName ++ ".new")++{- |+The lifting function for binary lenses. 'unlift2' is a left inverse of this function. ++prop> unlift2 (lift2 l) = l++This function can be defined from 'lift' and 'pair' as below.++prop> lift2 l x y = lift l (pair x y)++NB: This is not a right inverse of 'unlift2'.++prop> (\x y -> x) /= lift2 (unlift2 (\x y -> x))++>>> set (unlift (\z -> (\x y -> x) z z)) "A" "B"+"B"+>>> set (unlift (\z -> lift2 (unlift2 (\x y -> x)) (z,z))) "A" "B"+Error: ...+-}+lift2 :: L.Lens' (a,b) c -> (L s a -> L s b -> L s c)+lift2 l x y = lift l (pair x y) ++{- Derived Functions -}++{- | Similar to @pair@, but this function is for lists. This is a+derived function, because this can be defined by using 'lift' and+'pair'.+-}+list :: [L s a] -> L s [a]+list [] = lift (L.lens (\() -> [])+ (\() v -> case v of+ [] -> ()+ _ -> throw (ShapeMismatchException $ mName ++ ".list") ))+ unit+list (z:zs) = lift consL (pair z (list zs))+ where+ consL = L.lens (uncurry (:))+ (\_ z -> case z of+ (x:xs) -> (x,xs)+ _ -> throw (ShapeMismatchException $ mName ++ ".list"))++{- | A data-type generic version of 'list'. The contraint @Eq (t ())@+says that we can check the equivalence of shapes of containers @t@. -}+sequenceL :: (Eq (t ()), Traversable t) => t (L s a) -> L s (t a)+sequenceL x = lift (fillL x) $ list (contents x)+ where+ fillL t = L.lens (fill t)+ (\_ v -> if shape t == shape v then+ contents v+ else+ throw (ShapeMismatchException $ mName ++ ".sequenceL"))++{-# SPECIALIZE sequenceL :: [L s a] -> L s [a] #-} ++{- | A lifting function for lens combinators. One can understand that the+ universal quantification for the second argument as closedness restriction. -}+liftC :: Eq a => (L.Lens' a b -> L.Lens' c d) ->+ (forall s. L s a -> L s b) ->+ (forall s. L s c -> L s d)+liftC c f = lift (c (unlift f))++{- | Similar to 'liftC', but this function is for binary lens combinators.+-}+liftC2 :: (Eq a, Eq c) => (L.Lens' a b -> L.Lens' c d -> L.Lens' e f) + -> (forall s. L s a -> L s b) + -> (forall s. L s c -> L s d)+ -> (forall s. L s e -> L s f)+liftC2 c f g = lift (c (unlift f) (unlift g))++----------------------------------------------------------+{- | A datatype-generic version of 'lift2'-}+liftT :: (Eq (t ()), Traversable t)+ => L.Lens' (t a) b -> (forall s. t (L s a) -> L s b)+liftT l xs = lift l (sequenceL xs)++{- | Lifting of observations.+A typical use of this function would be as follows. ++@+f x :: L s Int -> R s (L s B)+f x = do b <- liftO (> 0) x + if b then ... else ... +@++-}+liftO :: Eq w => (a -> w) -> L s a -> R s w+liftO p x = observe (lift (L.lens p unused) x)+ where+ unused s v | v == p s = s+ | otherwise = error "This error cannot happen"++{- | Lifting of binary observations -}+liftO2 :: Eq w => (a -> b -> w) -> L s a -> L s b -> R s w+liftO2 p x y = liftO (uncurry p) (x `pair` y)
+ Control/LensFunction/Core.hs view
@@ -0,0 +1,419 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances #-}+{-# LANGUAGE IncoherentInstances #-}+-- Required for unliftM, unliftM2, unliftMT, if we use var Laarhoven repl.+{-# LANGUAGE NoMonomorphismRestriction #-}++{-# LANGUAGE Trustworthy #-}++module Control.LensFunction.Core where+ +import Prelude -- hiding ((.), id, sequence) +-- import Control.Category++import Data.Traversable (Traversable)+import Control.Applicative (Applicative, pure, (<*>))+import Control.Monad (ap)++import Control.LensFunction.Util+import Control.LensFunction.Exception+import Control.LensFunction.Internal++import qualified Control.Lens as L (Lens', lens)++import qualified Data.IntMap as IM++import Data.Maybe (fromJust) +import Control.Exception +++{- | +A variant of 'Control.Lens.lens'. Sometimes, this function would be+easier to use because one can re-use a source information to define a "put". +-}+lens' :: (s -> (v, v -> s)) -> L.Lens' s v+lens' f = \u s -> let (v,r) = f s+ in fmap r (u v)+{-# INLINE[1] lens' #-}++{- |+Just a composition of 'lift' and 'Control.Lens.lens'.+Sometimes, this function would be more efficient than the composition+due to eliminated conversion from the lens to the internal representation.++Since both of the internal and the external representations are+functions (= normal forms), we have to pay the conversion cost for+each time when the lifted lens function is evaluated, even in the lazy+evaluation.++We actually has the RULE to make the composition of 'lift' and+'Control.Lens.lens' to 'liftLens'. However, the rule may not be fired+especially when profiling codes are inserted by GHC.+-}+liftLens :: (a -> b) -> (a -> b -> a) -> (forall s. L s a -> L s b)+liftLens g p = liftI (lensI g p)++{- |+Just a composition of 'lift' and 'lens''. This function has the+similar role to 'liftLens'.+-}++liftLens' :: (a -> (b, b -> a)) -> (forall s. L s a -> L s b)+liftLens' f = liftI (lensI' f)++++dup :: Poset s => LensI s (s,s)+dup = lensI' $ \s -> ((s,s), \(t,t') -> lub t t')+{-# INLINE dup #-}+++{- | A type class for partially ordered sets, in which pairs are+compaired componentwisely. Instead of having partial comparison+operator, it provides an associative, commutative and idempotent+operator, which can be partial like "join" in semilattice but can be+partial.+-} +class Poset s where+ lub :: s -> s -> s -- ^ Operation to take LUB ++data Tag a = O { unTag :: a } -- Original, or irrelevant+ | U { unTag :: a } -- Updated, or relevant ++instance Eq a => Poset (Tag a) where+ lub (O a) (O b) | a == b = O a+ lub (O _) (U b) = U b+ lub (U a) (O _) = U a+ lub (U a) (U b) | a == b = U a+ lub _ _ = throw (NoLUBException "Control.LensFunction.lub")++instance (Poset a, Poset b) => Poset (a,b) where+ lub (a,b) (a',b') = (lub a a', lub b b')++instance (Poset a, Eq (t ()), Traversable t) => Poset (t a) where+ {-# SPECIALIZE lub :: Poset a => [a] -> [a] -> [a] #-}+ lub t1 t2 = if shape t1 == shape t2 then+ fill t1 (zipWith lub (contents t1) (contents t2))+ else+ throw (NoLUBException "Control.LensFunction.lub")+ +-- Internally used datastructure for a slightly first merge+data Diff t a = Diff (t ()) -- Shape of the data -- Assumption: t is Traversable+ (IM.IntMap a) -- Original mapping from indices to values + (IM.IntMap (Tag a)) -- Updated mapping ++{-# SPECIALIZE toDiff :: [a] -> Diff [] a #-}+toDiff :: Traversable t => t a -> Diff t a+toDiff s = let om = IM.fromAscList $ zip [0..] (contents s)+ in Diff (shape s) om IM.empty+++{-# SPECIALIZE fromDiff :: Diff [] a -> [a] #-}+fromDiff :: Traversable t => Diff t a -> t a+fromDiff (Diff sh om um) =+ let cs = map (\i -> case IM.lookup i um of+ Just v -> unTag v+ Nothing -> fromJust $ IM.lookup i om) [0..]+ in fill sh cs ++instance Eq a => Poset (Diff t a) where+ lub (Diff t1 o1 m1) (Diff t2 o2 m2) -- Invariant: t1 == t2 and o1 == o2 + = Diff t1 o1 (IM.unionWith lub m1 m2)++++{- |+An abstract type for "updatable" data. Bidirectional programming+through our module is to write manipulation of this datatype.++++==== Categorical Notes++The type constructor @L s@ together with 'lift', 'unit' and 'pair'+defines a lax monoidal functor from the category of lenses to that of+Haskell functions. The 'lift' function does transfor a lens to a+function. The 'unit' and 'pair' functions are the core operations on+this lax monoidal functor. Any lifting functions defined in this+module can be defined by these three functions.+++-}+newtype L s a = L (Poset s => LensI s a)++unL :: L s a -> (Poset s => LensI s a)+unL (L s) = s+{-# INLINE unL #-}+++{-+f id' <<< tag <<< y+vs.+f y ++lift2 id :: L s A -> L s B -> L s (A,B)++f y = pair y y+++(y *** y) <<< dup +vs+(id' *** id') <<< dup <<< tag <<< y++y = (\x -> unTag y) (\_ v -> O v) --- NOT locally well-behaved++put ((y *** y) <<< dup) s (s,v) = _|_++put ((id' *** id') <<< dup <<< tag <<< y) s (s,v) = O v++-}++{- |+The lifting function. Note that it forms a functor from the+category of lenses to the category of sets and functions.++'unlift' is a left-inverse of this function.++prop> unlift (lift x) = x+-}+{-+Notice that 'lift' is not a surjection. That is,+there is a function such that @lift (unlift f) /= f@.+However such a function cannot be constructed with `lift`.+-}+lift :: L.Lens' a b -> (forall s. L s a -> L s b)+lift l = liftI (fromLens l)++liftI :: LensI a b -> (forall s. L s a -> L s b)+liftI h = \(L x) -> L (h <<< x)++{-# NOINLINE[1] lift #-}+{-# INLINE liftI #-}++{- | A paring function of @L s a@-typed values.+This function can be defined by 'lift2' as below.++prop> pair = lift2 (lens id (const id)) +-}+pair :: L s a -> L s b -> L s (a,b) +pair (L x) (L y) = L ((x *** y) <<< dup)++{-# INLINE pair #-}+++{- |+The unit element in the lifted world.++Let @elimUnitL@ and @elimUnitR@ are lenses defined as follows.++@+elimUnitL = lens (\(x,()) -> x) (\_ x -> (x,()))+elimUnitR = lens (\((),x) -> x) (\_ x -> ((),x))+@++Then, we have the following laws.++prop> lift2 elimUnitL x unit = x+prop> lift2 elimUnitR unit x = x+-}+unit :: L s ()+unit = L $ lensI' (\s -> ( (), \() -> s ) )+{-# INLINABLE unit #-}+++{- | The unlifting function, satisfying @unlift (lift x) = x@. -}+unlift :: Eq a => (forall s. L s a -> L s b) -> L.Lens' a b+unlift f = toLens $ unL (f id') <<< tag++id' :: L (Tag s) s +id' = L $ lensI unTag (const U)+{-# INLINE id' #-}++tag :: LensI s (Tag s)+tag = lensI O (const unTag)+{-# INLINE tag #-}++{- | The unlifting function for binary functions, satisfying+ @unlift2 (lift2 x) = x@.+-}+unlift2 :: (Eq a, Eq b) => (forall s. L s a -> L s b -> L s c) -> L.Lens' (a,b) c+unlift2 f = toLens $ unL (f fst' snd') <<< tag2+++fst' :: L (Tag a,b) a+fst' = L $ lensI (unTag . fst) (\(_,b) a -> (U a, b))+{-# INLINE fst' #-}++snd' :: L (a, Tag b) b +snd' = L $ lensI (unTag . snd) (\(a,_) b -> (a, U b))+{-# INLINE snd' #-}++tag2 :: LensI (a,b) (Tag a, Tag b)+tag2 = lensI (\(a,b) -> (O a, O b)) (\_ (a,b) -> (unTag a, unTag b))+{-# INLINE tag2 #-}++{- |+The unlifting function for functions that manipulate data structures,+satisfying @unliftT (liftT x) = x@ if @x@ keeps the shape.+The constraint @Eq (t ())@ says that we can compare the shapes of+given two containers. ++-}+unliftT :: (Eq a, Eq (t ()), Traversable t) =>+ (forall s. t (L s a) -> L s b) -> L.Lens' (t a) b+unliftT f = toLens $ + lensI' $ \s -> let l = makeLens s+ in viewrefl l s + where+-- makeLens s = unL (f (projs (shape s))) <<< tagT+ makeLens s = unL (f (projsV (shape s))) <<< diffL ++tagT :: Functor f => LensI (f s) (f (Tag s))+tagT = lensI (fmap O) (\_ -> fmap unTag)+{-# INLINE tagT #-}++diffL :: Traversable t => LensI (t a) (Diff t a)+diffL = lensI' $ \s -> (toDiff s, fromDiff)+{-# INLINE diffL #-}+++-- V is from Voigtlaender +projsV :: Traversable t => t b -> t (L (Diff t a) a)+projsV sh =+ let n = length (contents sh)+ in fill sh $ map (projV sh) [0..n-1]++projV :: Traversable t => t b -> Int -> L (Diff t a) a+projV _ i = L $ lensI' $ \(Diff s o _) ->+ ( fromJust (IM.lookup i o),+ \v -> Diff s o (IM.singleton i (U v)))+ + + ++projs :: Traversable t => t b -> t (L (t (Tag a)) a)+projs sh =+ let n = length (contents sh)+ in fill sh $ map (proj sh) [0..n-1] +++proj :: Traversable t => t b -> Int -> L (t (Tag a)) a+proj sh i = L $+ lensI (\s -> unTag (contents s !! i))+ (\s v -> fill sh (update i (U v) (contents s)))++update :: Int -> a -> [a] -> [a]+update 0 v (_:xs) = v:xs+update i v (x:xs) = x:update (i-1) v xs +update _ _ _ = error "Invalid Index"++-- TODO: TH Generator for lifting and unlifting functions. +++---------------------------------------++{- |+An abstract monad used to keep track of observations.+By this monad, we can inspect the value of 'L s a'-data.++It is worth noting that we cannot change the inspection result to ensure+the consistency property (aka PutGet in some context).+++-}+newtype R s a = R { unR :: Poset s => s -> (a, s -> Bool) }++instance Functor (R s) where+ fmap f (R m) = R $ \s -> let (x, p) = m s in (f x, p)++instance Monad (R s) where+ return x = R $ const (x, const True)+ R m >>= f = R $ \s -> let (x,c1) = m s+ (y,c2) = let R k = f x in k s+ in (y, \t -> c1 t && c2 t)++instance Applicative (R s) where+ pure = return+ (<*>) = ap++ ++{- |+A primitive used to define 'liftO' and 'liftO2'.+With 'observe', one can inspect the current value of a lifted '(L s a)'-value+as below.++@+f x :: L s A -> R s (L s B)+f x = do viewX <- observe x+ ... computation depending on 'viewX' ...+@++Once the 'observe' function is used in a lens function,+the lens function becomes not able to change +change the "observed" value to ensure the correctness.++-}+observe :: Eq w => L s w -> R s w+observe l = R $ \s -> let w = get (unL l) s+ in (w, \s' -> get (unL l) s' == w)++{-# INLINABLE observe #-}++{- | A monadic version of 'unlift'. -}+unliftM :: Eq a => (forall s. L s a -> R s (L s b)) -> L.Lens' a b+unliftM f = toLens $ lensI' $ \src -> viewrefl (makeLens src) src + where+ makeLens src =+ let (l,p) = unR (f id') (O src)+ l' = unL l <<< tag+ put' s v =+ let s' = put l' s v+ in if p (O s') then+ s'+ else+ throw (ChangedObservationException "Control.Lens.Function.unliftM")+ in lensI (get l') put'++{- | A monadic version of 'unlift2'. -}+unliftM2 :: (Eq a, Eq b) =>+ (forall s. L s a -> L s b -> R s (L s c)) -> L.Lens' (a,b) c+unliftM2 f = toLens $ lensI' $ \src -> viewrefl (makeLens src) src + where+ makeLens src =+ let (l,p) = unR (f fst' snd') (get tag2 src)+ l' = unL l <<< tag2+ put' s v =+ let s' = put l' s v+ in if p (get tag2 s') then+ s'+ else+ throw (ChangedObservationException "Control.LensFunction.unliftM2")+ in lensI (get l') put'+++{- | A monadic version of 'unliftT'. -}+unliftMT :: (Eq a, Eq (t ()), Traversable t) =>+ (forall s. t (L s a) -> R s (L s b)) -> L.Lens' (t a) b+unliftMT f = toLens $ lensI' $ \src -> viewrefl (makeLens src) src+ where+ makeLens src =+ let (l,p) = unR (f (projsV (shape src))) (get diffL src)+ l' = unL l <<< diffL+ -- (l,p) = unR (f (projs (shape s))) (get tagT s)+ -- l' = unL l <<< tagT+ put' s v =+ let s' = put l' s v+ in if p (get diffL {- tagT -} s') then+ s'+ else+ throw (ChangedObservationException "Control.LensFunction.unliftMT")+ in lensI (get l') put'+ +{-# RULES+"lift/lens'" forall x. lift (lens' x) = liftI (lensI' x)+"lift/lens" forall g p. lift (L.lens g p) = liftI (lensI g p)+"lens/fromLens" forall g p. fromLens (L.lens g p) = lensI g p+"lens'/fromLens" forall f. fromLens (lens' f) = lensI' f+ #-}+
+ Control/LensFunction/Exception.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE DeriveDataTypeable #-}++module Control.LensFunction.Exception+ (+ SomeLensFunctionException(..),+ NoLUBException(..),+ ChangedObservationException(..),+ ShapeMismatchException(..),+ ConstantUpdateException(..)+ ) where++import Control.Exception+import Data.Typeable (cast, Typeable)++data SomeLensFunctionException+ = forall e. Exception e => SomeLensFunctionException e+ deriving Typeable++instance Show SomeLensFunctionException where+ show (SomeLensFunctionException e) = show e ++instance Exception SomeLensFunctionException ++lfToException :: Exception e => e -> SomeException+lfToException = toException . SomeLensFunctionException++lfFromException :: Exception e => SomeException -> Maybe e+lfFromException x = do+ SomeLensFunctionException a <- fromException x+ cast a++data NoLUBException = NoLUBException String deriving (Typeable)++instance Show NoLUBException where+ show (NoLUBException s) = s ++ ": No LUB"++instance Exception NoLUBException where+ toException = lfToException+ fromException = lfFromException++data ChangedObservationException+ = ChangedObservationException String+ deriving Typeable++instance Show ChangedObservationException where+ show (ChangedObservationException s) = s ++ ": Changed Observation"++instance Exception ChangedObservationException where+ toException = lfToException+ fromException = lfFromException+++data ShapeMismatchException = ShapeMismatchException String+ deriving Typeable ++instance Show ShapeMismatchException where+ show (ShapeMismatchException s) = s ++ ": Shape Mismatch"++instance Exception ShapeMismatchException where+ toException = lfToException+ fromException = lfFromException++data ConstantUpdateException = ConstantUpdateException String+ deriving Typeable +instance Show ConstantUpdateException where + show (ConstantUpdateException s) = s ++ ": Update on Constant"++instance Exception ConstantUpdateException where+ toException = lfToException+ fromException = lfFromException+ ++++
+ Control/LensFunction/Internal.hs view
@@ -0,0 +1,102 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE RankNTypes, ExistentialQuantification #-}+{-# LANGUAGE Trustworthy #-}+{-# LANGUAGE BangPatterns #-}++{- | Internal Representation of Lenses -}++module Control.LensFunction.Internal+ (+ LensI(), get, put+ , lensI, lensI', viewrefl+ , fromLens+ , toLens + , (***), (<<<)+ ) where++import qualified Control.Lens as L ++#ifdef __USE_VAN_LAARHOVEN__+import Control.LensFunction.InternalL+#else+#endif ++#ifndef __USE_VAN_LAARHOVEN__++newtype Store v s = Store (v, v -> s)++instance Functor (Store v) where+ {-# INLINE fmap #-}+ fmap = storeMap++storeMap :: (a -> b) -> Store v a -> Store v b +storeMap f (Store (v, !g)) = Store (v, f . g)+{-# INLINE storeMap #-}++fromLens :: L.Lens' s v -> LensI s v+fromLens lens = fromLens' lens -- the argument is necessary to pass the type check.++fromLens' :: ((v -> Store v v) -> (s -> Store v s)) -> LensI s v +fromLens' l = + -- lensI (getConst . l Const) (\s v -> L.runIdentity $ l (\_ -> L.Identity v) s)+ let f = l (\v -> Store (v,id))+ in LensI $ \s -> let Store !vr = f s+ in vr+{-# INLINE fromLens' #-} ++toLens :: LensI s v -> L.Lens' s v+toLens (LensI f) = \u s -> let (v, r) = f s+ in fmap r (u v)++{-# INLINE[0] fromLens #-}+{-# INLINE[0] toLens #-}++{-# RULES+"SPECIALIZE fromLens" forall (x :: L.Lens' s v). fromLens x = fromLens' (x :: (v -> Store v v) -> (s -> Store v s))+ #-}++{- |+A variant of conventional representation. +-}+newtype LensI s v = LensI { runLens :: s -> (v, v -> s) }++get :: LensI s v -> s -> v+get lens = fst . runLens lens+{-# INLINE get #-}++put :: LensI s v -> s -> v -> s+put lens = snd . runLens lens+{-# INLINE put #-}++lensI :: (s -> v) -> (s -> v -> s) -> LensI s v+lensI g p = LensI (\s -> (g s, p s))+{-# INLINE lensI #-}++lensI' :: (s -> (v, v -> s)) -> LensI s v+lensI' = LensI+{-# INLINE lensI' #-}++viewrefl :: LensI s v -> s -> (v, v -> s)+viewrefl = runLens+{-# INLINE viewrefl #-}++(<<<) :: LensI b c -> LensI a b -> LensI a c +y <<< x = LensI $ \s ->+ let !(v1, r1) = runLens x s+ !(v2, r2) = runLens y v1+ in (v2, r1 . r2)+{-# INLINABLE (<<<) #-}++(***) :: LensI a s -> LensI b t -> LensI (a,b) (s,t)+x *** y = LensI $ \(a,b) ->+ let !(va, ra) = runLens x a+ !(vb, rb) = runLens y b+ in ((va,vb), \(va',vb') -> (ra va', rb vb'))++{-# INLINABLE (***) #-}+++++#endif +
+ Control/LensFunction/InternalL.hs view
@@ -0,0 +1,59 @@+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE Safe #-}++{- | Lens in the van Laarhoven representation -}++module Control.LensFunction.InternalL where++import Control.Arrow (first, second)+import Control.Applicative (Const(..))+import Control.Monad.Identity ++import qualified Control.Lens as L +++type LensI s v = L.Lens' s v++fromLens :: L.Lens' s v -> LensI s v +fromLens x = x+{-# INLINE[2] fromLens #-}++toLens :: LensI s v -> L.Lens' s v+toLens x = x+{-# INLINE[2] toLens #-}++get :: LensI s v -> s -> v +get l = L.view l -- the argument is necessary to pass the type check.++{-# INLINE get #-}++put :: LensI s v -> s -> v -> s+put lens = flip (L.set lens) +{-# INLINE put #-}++newtype Store a b = Store { runStore :: (a, a -> b) }++instance Functor (Store a) where+ {-# INLINE fmap #-}+ fmap f (Store (a, r)) = Store (a, f . r)++viewrefl :: LensI s v -> (s -> (v, v -> s))+viewrefl lens s = runStore $ lens (\v -> Store (v, id)) s +{-# INLINE viewrefl #-}+++lensI :: (s -> v) -> (s -> v -> s) -> LensI s v+lensI = L.lens+{-# INLINE lensI #-}++lensI' :: (s -> (v, v -> s)) -> LensI s v+lensI' h = \f s -> let (v,r) = h s+ in fmap r (f v)+{-# INLINE lensI' #-}++(***) :: LensI a b -> LensI a' b' -> LensI (a,a') (b,b')+x *** y = L.alongside x y+{-# INLINE (***) #-}++(<<<) = flip (.)+{-# INLINE (<<<) #-}
+ Control/LensFunction/Util.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE Trustworthy #-}++module Control.LensFunction.Util where++import Data.Traversable (Traversable)+import qualified Data.Traversable as T+import qualified Data.Foldable as F++import qualified Control.Monad.State as St ++contents :: Traversable t => t a -> [a] +contents = F.toList++fill :: Traversable t => t b -> [a] -> t a+fill t = St.evalState (T.traverse next t)+ where+ next _ = do (y:ys) <- St.get+ St.put ys+ return y++{-# INLINABLE[2] fill #-}+{-# INLINABLE[2] contents #-} +{-# RULES+"fill/list" fill = fillList+"contents/list" contents = id :: [a] -> [a]+ #-}+fillList :: [a] -> [b] -> [b]+fillList _ ys = ys++shape :: Functor f => f a -> f ()+shape = fmap (const ())
+ Examples/Evaluator.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE NoMonomorphismRestriction #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DeriveFunctor, DeriveFoldable, DeriveTraversable #-}++module Examples.Evaluator where++import Control.LensFunction++import Data.Traversable (Traversable)+import Data.Foldable (Foldable) ++import Control.Lens++data Exp = ENum Integer+ | EInc Exp + | EFun String Exp + | EApp Exp Exp + | EVar String+ deriving (Eq, Show)++data Val a = VNum a + | VFun String Exp (Env a)+ deriving (Eq, Functor, Foldable, Traversable, Show) ++newtype Env a = Env [(String, Val a)]+ deriving (Eq, Functor, Foldable, Traversable, Show) ++lkup x (Env env) = case lookup x env of+ Just v -> v+ Nothing -> error $ "Undefined variable: " ++ x+xtnd (x,e) (Env env) = Env $ (x,e):env++incL = lens' $ \s -> (s + 1, \v -> v - 1)++eval :: Exp -> Env (L s Integer) -> Val (L s Integer)+eval (ENum n) env = VNum (new n)+eval (EInc e) env =+ let VNum n = eval e env+ in VNum (lift incL n)+eval (EFun x e) env =+ VFun x e env+eval (EApp e1 e2) env =+ let VFun x e env' = eval e1 env+ v2 = eval e2 env+ in eval e (xtnd (x,v2) env')+eval (EVar x) env = lkup x env++infixl 9 @@ -- @@ is left associative+(@@) = EApp++expr = twice @@ twice @@ twice @@ twice @@ inc @@ x + where+ twice = EFun "f" $ EFun "x" $+ EVar "f"@@ (EVar "f" @@ EVar "x")+ inc = EFun "x" (EInc (EVar "x"))+ x = EVar "x"++evalL e = unliftT (\env -> sequenceL $ eval e env)++env0 = Env [("x", VNum 3)]+envn n = Env $ [("x", VNum 3)] ++ [ ("y" ++ show i, VNum i) | i <- [1..n] ]++{-+*Examples.Evaluator> env0 ^. evalL expr+VNum 65539+*Examples.Evaluator> env0 & evalL expr .~ (VNum 0)+Env [("x",VNum (-65536))]+-}
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2015, Kazutaka Matsuda++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above+ copyright notice, this list of conditions and the following+ disclaimer in the documentation and/or other materials provided+ with the distribution.++ * Neither the name of Kazutaka Matsuda nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app-lens.cabal view
@@ -0,0 +1,198 @@+name: app-lens+version: 0.1.0.0+synopsis: applicative (functional) bidirectional programming beyond composition chains ++description:+ A bidirectional transformation connects data in difference formats,+ maintaining consistency amid separate updates. The "lens"+ programming language---with Kmett's Haskell lens package being+ one of the most influentials---is a solution to this problem.+ .+ Many lens implementations (including Kmett's Haskell library) only+ support the point-free style of programming. Though concise at times,+ this style becomes less handy when programs get more complicated. + . + This module provides the infrastructure for programming complex+ bidirectional transformations, by representing lenses as functions+ that are subject to the normal applicative-style programming. For+ example, let us consider the 'unlines' functions and to define a+ lens version of it. In our framework we can program through pattern+ matching and explicit recursion as in normal functional programming.+ .+ > unlinesF :: [L s String] -> L s String+ > unlinesF [] = new ""+ > unlinesF (x:xs) = catLineF x (unlinesF xs)+ > where catLineF = lift2 catLineL+ . + Here, @lift2 :: Lens' (a,b) c -> (forall s. L s a -> L s b -> L s+ c)@ and @new :: a -> (forall s. L s a)@ lift lenses to functions.+ The former is for binary lenses and the latter is for constant+ lenses. We can then apply lenses as functions, alleviating the+ need of specialized combinators. In the above, we omitted the+ definition of a primitive lens @catLineL :: Lens' (String, String)+ String@ that concatenates two strings with a newline in between. + .+ Simply unlifting ('unlift', 'unlift2', 'unliftT') such "lens functions"+ gives us the desired lenses. + . + > unlinesL :: Lens' [String] String+ > unlinesL = unliftT unlinesF+ .+ The obtained lens works as expected.+ .+ .+ >>> ["banana", "orange", "apple"] ^. unlinesL+ "banana\norange\napple\n"+ >>> ["banana", "orange", "apple"] & unlinesL .~ "Banana\nOrange\nApple\n"+ ["Banana","Orange","Apple"]+ .+ . + One may prefer to define @unliftF@ with 'foldr'. Indeed, we can+ use 'foldr' as below because @catLineF@ and @unlinesF@ are simply+ Haskell functions.+ .+ > unliftF = foldr (lift2 catLineL) (new "") + .+ Here, the program is written in a point-free manner similar to that+ of the other lens frameworks. But note that this 'foldr' is just+ Haskell's 'foldr', instead of a special combinator for lenses.+ .+ More examples can be found at \"Examples\" in the source code+ <https://bitbucket.org/kztk/app-lens/downloads>.+ . + === Remark+ .+ The applicative-style programming is possible in our implementation + because a function representation different from Kmett's is used for lenses.+ As a result, when we program record-field access chains such as + .+ > src .^ l1 . l2 + > src & l1 . l2 .~ tgt' + .+ The order of composition is inverted in our implementation. + .+ > src .^ unlift (lift l2 . lift l1)+ > src & unlift (lift l2 . lift l1) .~ tgt' + .+ This difference causes slight inconvenience for record updates, but is + crucial in allowing the applicative-style lens programming we+ aim for. +++license: BSD3+license-file: LICENSE++homepage: https://bitbucket.org/kztk/app-lens+bug-reports: https://bitbucket.org/kztk/app-lens/issues+tested-with: GHC == 7.8.3++author: Kazutaka Matsuda+copyright: (c) Kazutaka Matsuda, 2015+maintainer: kztk@ecei.tohoku.ac.jp+category: Data, Lenses+build-type: Simple+cabal-version: >=1.10++++Flag UseVanLaarhoven+ Description: Use Control.Lens.Lens' as internal representations.+ (1.5 times speed up for 'lift' but 1000 times slow down for 'lift2')+ Default: False ++Library+ exposed-modules:+ Control.LensFunction, + Control.LensFunction.Exception+ + other-modules: + Control.LensFunction.Core, + Control.LensFunction.Util + Control.LensFunction.Internal + + if flag(useVanLaarhoven)+ other-modules: Control.LensFunction.InternalL+ cpp-options: -D__USE_VAN_LAARHOVEN__++ other-extensions: + RankNTypes, NoMonomorphismRestriction, + FlexibleInstances, FlexibleContexts, UndecidableInstances, + IncoherentInstances, CPP, ExistentialQuantification, + DeriveDataTypeable, DeriveFunctor, DeriveFoldable, DeriveTraversable+ + build-depends: + base >=4.7 && < 4.8, + containers >=0.5 && < 0.6, + mtl >=2.2 && < 2.3,+ lens >=4 && < 4.12++ default-language: Haskell2010++-- Executable prof+-- Main-is: Bench/Prof.hs+-- Build-Depends:+-- app-lens, +-- base,+-- mtl, +-- containers, +-- lens, +-- deepseq >= 1.3 && < 2, +-- criterion >= 1.1 && < 2+--+--+-- if flag(useVanLaarhoven)+-- cpp-options: -D__USE_VAN_LAARHOVEN__+--+-- ghc-options: -O2 -rtsopts +-- ghc-prof-options: -prof -auto-all -rtsopts "-with-rtsopts=-p -s"+-- default-language: Haskell2010+++Benchmark compositions+ type: exitcode-stdio-1.0+ Main-is: Bench/Compositions.hs+ Build-Depends: + app-lens, + base,+ mtl, + containers, + lens, + deepseq >= 1.3 && < 1.4, + criterion >= 1.1 && < 1.2+++ if flag(useVanLaarhoven)+ cpp-options: -D__USE_VAN_LAARHOVEN__++ ghc-options: -rtsopts -O2+ ghc-prof-options: -prof -rtsopts + default-language: Haskell2010++Benchmark eval+ type: exitcode-stdio-1.0+ Main-is: Bench/Eval.hs+ Build-Depends: + app-lens, + base,+ mtl, + containers, + lens,+ deepseq >= 1.3 && < 1.4, + criterion >= 1.1 && < 1.2++ other-modules: + Examples.Evaluator++ if flag(useVanLaarhoven)+ cpp-options: -D__USE_VAN_LAARHOVEN__++ ghc-options: -rtsopts -O2+ ghc-prof-options: -prof -rtsopts + default-language: Haskell2010++source-repository head+ type: git+ location: https://bitbucket.org/kztk/app-lens+++