packages feed

pointless-lenses-0.0.8: src/Generics/Pointless/DLenses.hs

-----------------------------------------------------------------------------
-- |
-- Module      :  Generics.Pointless.DLenses
-- Copyright   :  (c) 2011 University of Minho
-- License     :  BSD3
--
-- Maintainer  :  hpacheco@di.uminho.pt
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Pointless Lenses:
-- bidirectional lenses with point-free programming
-- 
-- This module defines the structure of delta lenses and provides Quickcheck procedures to test delta-lens well-behavedness.
--
-----------------------------------------------------------------------------

module Generics.Pointless.DLenses where

import Data.Relation
import Data.Shape
import Data.Diff
import Generics.Pointless.Lenses (Lens)
import qualified Generics.Pointless.Lenses as Lns

import qualified Data.Set as Set
import Test.QuickCheck.Gen
import Test.QuickCheck.Arbitrary
import Test.QuickCheck

-- | The data type of Diskin's et al delta lenses (http://dx.doi.org/10.5381/jot.2011.10.1.a6)
data DeltaLens s a v b = DeltaLens { get0 :: s a -> v b
                                   , getdelta :: s a -> s a -> Delta (s a) (s a) -> Delta (v b) (v b)
                                   , put0 :: (v b,s a) -> Delta (v b) (v b) -> s a
                                   , putdelta :: v b -> s a -> Delta (v b) (v b) -> Delta (s a) (s a) }

delta_lns :: (Shapely s,Shapely v) => DLens s a v b -> DeltaLens s a v b
delta_lns l = DeltaLens get0' getdelta' put0' putdelta'
    where get0' s = get l s
          getdelta' s' s dS = inv (getd l s) .~ dS .~ getd l s'
          put0' (v,s) dV = put l (v,s) dV
          putdelta' v s dV = eitherPosR (v,s) (getd l s .~ dV) (locsR s) .~ putd l v s dV

-- | The data type of (horizontal) delta lenses
data DLens s a v b = DLens { get     :: s a -> v b
                           , getd    :: s a -> Delta (v b) (s a)                                 -- delta (get s) s
                           , put     :: (v b,s a) -> Delta (v b) (v b) -> s a                    -- (v,s) -> delta v (get s) -> S
                           , putd    :: v b -> s a -> Delta (v b) (v b) -> Delta (s a) (v b,s a) -- v -> s -> d:delta v (get s) -> delta (put (v,s) d) (v,s)
                           , create  :: v b -> s a
                           , created :: v b -> Delta (s a) (v b)                                 -- v -> delta (create v) v
                           }

-- | The type of natural delta lenses.
type NatDLens s v = forall a. DLens s a v a

-- | Converts a delta lens, for a specific differencing operation, to a normal lens
embed_lns :: Shapely v => Diff (v b) -> DLens s a v b -> Lens (s a) (v b)
embed_lns diff l = Lns.Lens get' put' create'
    where get' = get l
          put' (v,s) = put l (v,s) (diff v (get l s) .~ getd l s)
          create' = create l

-- ** QuickCheck testing for well-behaved delta lenses

data TestArgs s a v b = TestArgs (v b) (s a) (Delta (v b) (v b)) deriving Show

-- | Generates a view, source and delta update triple
testGen :: (Arbitrary (s a),Arbitrary (v b),Shapely s,Shapely v) => DLens s a v b -> Gen (TestArgs s a v b)
testGen l = do
	v <- arbitrary
	s <- arbitrary
	dV <- deltaGen v (get l s)
	return $ TestArgs v s dV

-- | Generates a delta update (a partial function from positions to positions) between two values
deltaGen :: Shapely v => v b -> v b -> Gen (Delta (v b) (v b))
deltaGen v' v = locsGen (Set.toList $ locs v') (Set.toList $ locs v)
locsGen :: [Int] -> [Int] -> Gen (Int :->: Int)
locsGen [] l = return Set.empty
locsGen (i:is) l = do { x <- locGen i l; y <- locsGen is l; return (Set.union x y) }
locGen :: Int -> [Int] -> Gen (Int :->: Int)
locGen iv [] = return Set.empty
locGen iv l = oneof [do { is <- elements l; return (Set.singleton (iv,is)) },return Set.empty]

testDLens l = quickCheck $ forAll (testGen l) (wbDelta l)
    where wbDelta l (TestArgs v s dV) = wb l v s dV

-- | QuickCheck procedure to test if a lens is well-behaved.
wb :: (Eq (s a),Eq (v b),Shapely s,Shapely v) => DLens s a v b -> v b -> s a -> Delta (v b) (v b) -> Bool
wb l v s dV = putget l v s dV && getput l s && createget l v && putgetd l v s dV && getputd l s && creategetd l v

-- | QuickCheck procedure to test if a lens satisfies the PutGet law.
putget :: (Eq (s a),Eq (v b),Shapely s,Shapely v) => DLens s a v b -> v b -> s a -> Delta (v b) (v b) -> Bool
putget l v s dV = get l (put l (v,s) dV) == v

-- | QuickCheck procedure to test if a lens satisfies the GetPut law.
getput :: (Eq (s a),Shapely s,Shapely v) => DLens s a v b -> s a -> Bool
getput l s = let v = get l s in put l (get l s,s) (locsR v) == s

-- | QuickCheck procedure to test if a lens satisfies the CreateGet law.
createget :: (Eq (v b),Shapely s,Shapely v) => DLens s a v b -> v b -> Bool
createget l v = get l (create l v) == v

-- | QuickCheck procedure to test if a lens satisfies the PutGetDelta law.
putgetd :: (Shapely s,Shapely v) => DLens s a v b -> v b -> s a -> Delta (v b) (v b) -> Bool
putgetd l v s dV = let s' = put l (v,s) dV in putd l v s dV .~ getd l s' == inlPosR (v,s)

-- | QuickCheck procedure to test if a lens satisfies the GetPutDelta law.
getputd :: (Shapely s,Shapely v) => DLens s a v b -> s a -> Bool
getputd l s = let v = get l s in (eitherPosR (v,s) (getd l s) (locsR s)) .~ putd l (get l s) s (locsR v) == locsR s

-- | QuickCheck procedure to test if a lens satisfies the CreateGetDelta law.
creategetd :: (Shapely s,Shapely v) => DLens s a v b -> v b -> Bool
creategetd l v = let s = create l v in created l v .~ getd l s == locsR v