existential-0.1.0.0: src/Data/Existential.hs
{-# LANGUAGE KindSignatures
,ConstraintKinds
,ExistentialQuantification
#-}
-- | Conventions:
-- * fooCell: a function that applies to 'Cell's directly
-- * fooCell': a function, very similar to fooCell but
-- that applies to a type which wraps a 'Cell' using
-- an instance of 'HasCell'
-- * fooCell1: a function that applies to 'Cell1'
-- * fooCell1': a function that a applies to a wrapped Cell1
-- (depends on 'HasCell')
module Data.Existential where
import Control.Applicative as A
import Control.Lens
import Control.Monad
import Data.Maybe
import Data.Typeable
import GHC.Exts (Constraint)
import Language.Haskell.TH
import Test.QuickCheck
import Text.Printf
-- |
-- = The Cell Type
-- | A polymorphic cell. Type 'Cell MyClass' can take a value of any
-- type that conforms to 'MyClass' and to 'Typeable'. It is defined
-- in terms of 'Cell1'.
type Cell = Cell1 Identity
data Cell1 f (constr :: * -> Constraint) = forall a. (constr a, Typeable a) => Cell (f a)
-- ^ Generilization of 'Cell'. 'Cell1 MyFunctor MyClass' takes values
-- ^ of type 'MyFunctor a' with '(MyClass a,Typeable a)'.
-- | 'HasCell' permits the overloading of "Iso" 'cell' and makes it easier
-- | to wrap a 'Cell' with a newtype without having to mention 'Cell' all
-- | the time.
class HasCell a b | a -> b where
cell :: Iso' a b
instance HasCell (Cell1 f constr) (Cell1 f constr) where
cell = id
-- |
-- = Contructors
makeCell :: (HasCell a (Cell constr), constr b, Typeable b)
=> b -> a
-- ^ We can use 'makeCell "hello" :: MyType' if there is an instance
-- ^ 'HasCell MyType (Cell Show)' (or any other class than show).
makeCell = makeCell1 . Identity
-- ^ Similar to 'makeCell'. Uses 'Cell1' to allow the content
-- ^ of a 'Cell' to be wrapped with a generic type.
makeCell1 :: (HasCell a (Cell1 f constr), constr b, Typeable b)
=> f b -> a
makeCell1 x = Cell x ^. from cell
-- |
-- = Prisms
_Cell :: (constr b,Typeable b,Typeable a) => Prism (Cell constr) (Cell constr) a b
-- ^ Treats a 'Cell' as an unbounded sum type: 'c^?_Cell :: Maybe a' has the
-- ^ value 'Just x' if x is of type 'a' and 'c' contains value 'x'. If cell 'c'
-- ^ has a value of any other type then 'a', 'c^?_Cell == Nothing'.
_Cell = _Cell1._Wrapped
_Cell' :: (constr a,Typeable a,HasCell c (Cell constr)) => Prism c c a a
-- ^ Similar to '_Cell' but operates on types that wrap a cell instead of
-- ^ on the cell itself.
_Cell' = cell.asCell
_Cell1 :: (constr b,Typeable b,Typeable a,Typeable f)
=> Prism (Cell1 f constr) (Cell1 f constr) (f a) (f b)
-- ^ Similar to '_Cell' but values are wrapped in type 'f' inside the cell.
_Cell1 = prism Cell $ \x -> maybe (Left x) Right $ readCell1 cast x
_Cell1' :: (constr a,Typeable a,Typeable f,HasCell c (Cell1 f constr)) => Prism c c (f a) (f a)
-- ^ Analogous to '_Cell'' and '_Cell1'.
_Cell1' = cell.asCell1
asCell :: (constr a,Typeable a) => Prism (Cell constr) (Cell constr) a a
-- ^ Like '_Cell' but disallows changing the type of the content of the cell.
-- ^ facilitates type checking when the prism is not used for modification.
asCell = _Cell
asCell1 :: (constr a,Typeable a,Typeable f) => Prism (Cell1 f constr) (Cell1 f constr) (f a) (f a)
-- ^ Like '_Cell1' and as 'asCell'.
asCell1 = _Cell1
-- |
-- = Traversals
traverseCell :: Functor f => (forall a. (constr a,Typeable a) => a -> f a)
-> Cell constr -> f (Cell constr)
traverseCell f = traverseCell1 $ _Wrapped f
traverseCell' :: (Functor f,HasCell c (Cell constr))
=> (forall a. (constr a,Typeable a) => a -> f a) -> c -> f c
traverseCell' f = cell (traverseCell f)
traverseCell1 :: Functor f => (forall a. (constr a,Typeable a) => g a -> f (h a))
-> Cell1 g constr -> f (Cell1 h constr)
traverseCell1 f (Cell x) = Cell <$> f x
traverseCell1' :: (Functor f,HasCell c (Cell1 g constr))
=> (forall a. (constr a,Typeable a) => g a -> f (g a)) -> c -> f c
traverseCell1' f = cell (traverseCell1 f)
mapCell :: (forall a. (constr a,Typeable a) => a -> a) -> Cell constr -> Cell constr
mapCell f = runIdentity . traverseCell (Identity . f)
mapCell' :: HasCell c (Cell constr)
=> (forall a. (constr a,Typeable a) => a -> a)
-> c -> c
mapCell' f x = mapCell f (x^.cell) ^. from cell
readCell1 :: (forall a. (constr a,Typeable a) => f a -> r) -> Cell1 f constr -> r
readCell1 = readCell1'
readCell1' :: HasCell c (Cell1 f constr)
=> (forall a. (constr a,Typeable a) => f a -> r)
-> c -> r
readCell1' f = getConst . traverseCell1' (Const . f)
readCell :: (forall a. (constr a,Typeable a) => a -> r) -> Cell constr -> r
readCell f = getConst . traverseCell (Const . f)
readCell' :: HasCell c (Cell constr)
=> (forall a. (constr a,Typeable a) => a -> r)
-> c -> r
readCell' f x = readCell f $ x^.cell
-- |
-- = Combinators =
apply2Cells :: Functor f
=> (forall a. (constr a,Typeable a)
=> a -> a -> f a)
-> f (Cell constr)
-> Cell constr -> Cell constr
-> f (Cell constr)
apply2Cells f = apply2Cells1 (\(Identity x) (Identity y) -> Identity <$> f x y)
apply2Cells' :: (Functor f,HasCell c (Cell constr))
=> (forall a. (constr a,Typeable a)
=> a -> a -> f a)
-> f c -> c -> c -> f c
apply2Cells' f def x y = view (from cell) <$> apply2Cells f (view cell <$> def) (x^.cell) (y^.cell)
apply2Cells1 :: (Functor f,Typeable g)
=> (forall a. (constr a,Typeable a)
=> g a -> g a -> f (g a))
-> f (Cell1 g constr)
-> Cell1 g constr -> Cell1 g constr
-> f (Cell1 g constr)
apply2Cells1 f def (Cell x) (Cell y) = fromMaybe def $ fmap Cell . f x <$> cast y
apply2Cells1' :: (Functor f,Typeable g,HasCell c (Cell1 g constr))
=> (forall a. (constr a,Typeable a)
=> g a -> g a -> f (g a))
-> f c
-> c -> c
-> f c
apply2Cells1' f def x y = view (from cell) <$> apply2Cells1 f (view cell <$> def) (x^.cell) (y^.cell)
map2Cells :: (forall a. (constr a,Typeable a)
=> a -> a -> a)
-> Cell constr -> Cell constr -> Cell constr
-> Cell constr
map2Cells f def x y = runIdentity $ apply2Cells (fmap pure . f) (pure def) x y
map2Cells' :: HasCell c (Cell constr)
=> (forall a. (constr a,Typeable a)
=> a -> a -> a)
-> c -> c -> c -> c
map2Cells' f def x y = view (from cell) $ map2Cells f (def^.cell) (x^.cell) (y^.cell)
map2Cells1 :: (forall a. (constr a,Typeable a)
=> a -> a -> a)
-> Cell constr -> Cell constr -> Cell constr
-> Cell constr
map2Cells1 f def x y = runIdentity $ apply2Cells (fmap pure . f) (pure def) x y
map2Cells1' :: HasCell c (Cell constr)
=> (forall a. (constr a,Typeable a)
=> a -> a -> a)
-> c -> c -> c -> c
map2Cells1' f def x y = view (from cell) $ map2Cells f (def^.cell) (x^.cell) (y^.cell)
read2CellsWith :: (forall a. (constr a,Typeable a) => a -> a -> r) -> r -> Cell constr -> Cell constr -> r
read2CellsWith f = read2Cells1With $ onIdentity f
read2CellsWith' :: HasCell c (Cell constr)
=> (forall a. (constr a,Typeable a) => a -> a -> r)
-> r -> c -> c -> r
read2CellsWith' f def x y = read2CellsWith f def (x^.cell) (y^.cell)
read2Cells1With :: Typeable f
=> (forall a. (constr a,Typeable a) => f a -> f a -> r)
-> r -> Cell1 f constr -> Cell1 f constr -> r
read2Cells1With f x = fmap getConst . apply2Cells1 (fmap Const . f) (Const x)
read2Cells1With' :: (HasCell c (Cell1 f constr),Typeable f)
=> (forall a. (constr a,Typeable a) => f a -> f a -> r)
-> r -> c -> c -> r
read2Cells1With' f def x y = read2Cells1With f def (x^.cell) (y^.cell)
-- |
-- = Heterogenous Combinators
read2CellsH :: (forall a b. (constr a,Typeable a,constr b,Typeable b) => a -> b -> r)
-> Cell constr -> Cell constr -> r
read2CellsH f (Cell x) (Cell y) = f (runIdentity x) (runIdentity y)
read2CellsH' :: HasCell c (Cell constr)
=> (forall a b. (constr a,Typeable a,constr b,Typeable b) => a -> b -> r)
-> c -> c -> r
read2CellsH' f x y = read2CellsH f (x^.cell) (y^.cell)
read2Cells1H :: (forall a b. (constr a,Typeable a,constr b,Typeable b) => f a -> f b -> r)
-> Cell1 f constr -> Cell1 f constr -> r
read2Cells1H f (Cell x) (Cell y) = f x y
read2Cells1H' :: (forall a b. (constr a,Typeable a,constr b,Typeable b) => f a -> f b -> r)
-> Cell1 f constr -> Cell1 f constr -> r
read2Cells1H' f x y = read2Cells1H f (x^.cell) (y^.cell)
-- |
-- = Comparing the content of cells
cell1Equal :: Typeable f
=> (forall a. constr a => f a -> f a -> Bool)
-> Cell1 f constr
-> Cell1 f constr
-> Bool
cell1Equal f = read2Cells1With f False
cell1Equal' :: (HasCell c (Cell1 f constr),Typeable f)
=> (forall a. constr a => f a -> f a -> Bool)
-> c -> c -> Bool
cell1Equal' f x y = cell1Equal f (x^.cell) (y^.cell)
cellEqual :: (forall a. constr a => a -> a -> Bool)
-> Cell constr
-> Cell constr
-> Bool
cellEqual f = read2CellsWith f False
cellEqual' :: HasCell c (Cell constr)
=> (forall a. constr a => a -> a -> Bool)
-> c -> c -> Bool
cellEqual' f x y = cellEqual f (x^.cell) (y^.cell)
cellCompare :: (forall a. constr a => a -> a -> Ordering)
-> Cell constr
-> Cell constr
-> Ordering
cellCompare = cellCompare'
cellCompare' :: HasCell c (Cell constr)
=> (forall a. constr a => a -> a -> Ordering)
-> c -> c -> Ordering
cellCompare' f = cell1Compare' $ onIdentity f
cell1Compare :: (Typeable f)
=> (forall a. constr a => f a -> f a -> Ordering)
-> Cell1 f constr
-> Cell1 f constr
-> Ordering
cell1Compare f x y = read2Cells1With f (x' `compare` y') x y
where
x' = readCell1 typeOf x :: TypeRep
y' = readCell1 typeOf y :: TypeRep
cell1Compare' :: (HasCell c (Cell1 f constr),Typeable f)
=> (forall a. constr a => f a -> f a -> Ordering)
-> c -> c -> Ordering
cell1Compare' f x y = cell1Compare f (x^.cell) (y^.cell)
-- |
-- = Creating Lenses
cellLens :: Functor f => (forall a. constr a => LensLike' f a b) -> LensLike' f (Cell constr) b
cellLens = cellLens'
cellLens' :: (HasCell c (Cell constr), Functor f)
=> (forall a. constr a => LensLike' f a b)
-> LensLike' f c b
cellLens' ln f = traverseCell' (ln f)
cell1Lens :: Functor f => (forall a. constr a => LensLike' f (g a) b) -> LensLike' f (Cell1 g constr) b
cell1Lens = cell1Lens'
cell1Lens' :: (HasCell c (Cell1 g constr), Functor f)
=> (forall a. constr a => LensLike' f (g a) b)
-> LensLike' f c b
cell1Lens' ln f = traverseCell1' (ln f)
-- |
-- = QuickCheck Helpers
arbitraryCell :: Name -> ExpQ
arbitraryCell cl = arbitraryCell' cl []
arbitraryCell' :: Name -> [TypeQ] -> ExpQ
arbitraryCell' cl ts = [e| $(arbitraryInstanceOf' 'Cell cl ts) :: Gen (Cell $(conT cl)) |]
arbitraryInstanceOf :: Name -> Name -> ExpQ
arbitraryInstanceOf cons cl = arbitraryInstanceOf' cons cl []
arbitraryInstanceOf' :: Name -> Name -> [TypeQ] -> ExpQ
arbitraryInstanceOf' cons cl ts = do
ClassI _ is <- reify cl
ts <- sequence ts
let getArg (InstanceD [] (AppT _ t) [])
| t `notElem` ts = return (Just t)
| otherwise = return Nothing
getArg t = do
reportError $ "invalid number of arguments in instance: " ++ pprint t
return Nothing
--trigger x =
is' <- catMaybes <$> mapM (fmap (fmap return) . getArg) is
let arbits = [ [e| $(conE cons) <$> $(arb i) |] | i <- is' ]
arb i = sigE [e| arbitrary |] [t| Gen $i |]
when (null is') $ fail $ printf "no instances of '%s' found" (show cl)
[e| oneof $(listE arbits) |]
-- |
-- = Utilities
-- | Utility function to facilitate the implementation of 'Cell'
-- | functions in terms of 'Cell1' functions.
onIdentity :: (a -> b -> c)
-> Identity a -> Identity b
-> c
onIdentity f (Identity x) (Identity y) = f x y
-- |
-- = Properties
-- | Wrapping two values in cells does not change their equality
prop_consistent_equal :: (Eq a,Typeable a) => a -> a -> Property
prop_consistent_equal x y = cellEqual (==) (makeCell' x) (makeCell' y) === (x == y)
where
makeCell' = makeCell :: (Eq a,Typeable a) => a -> Cell Eq
-- | Wrapping two values in cells does not change their relative order
prop_consistent_compare :: (Ord a,Typeable a) => a -> a -> Property
prop_consistent_compare x y = cellCompare compare (makeCell' x) (makeCell' y) === (x `compare` y)
where
makeCell' = makeCell :: (Ord a,Typeable a) => a -> Cell Ord
return []
-- | Check all the QuickCheck properties.
run_tests :: IO Bool
run_tests = $quickCheckAll