-- | -- Module : Data.Express.Instances -- Copyright : (c) 2019-2020 Rudy Matela -- License : 3-Clause BSD (see the file LICENSE) -- Maintainer : Rudy Matela -- -- Defines utilities do deal with instances of typeclasses -- -- Functions provided by this module store the set of instances as a simple -- Haskell list. When storing only a few instances this should be fine in -- terms of performance. -- -- If you plan to store hundreds or thousands of instances, -- we recommend implementing different versions that use -- a more efficient Set/Map storage. module Data.Express.Instances ( reifyEq , reifyOrd , reifyEqOrd , reifyName , mkEq , mkOrd , mkOrdLessEqual , mkName , mkNameWith , isEq , isOrd , isEqOrd , isEqT , isOrdT , isEqOrdT , mkEquation , mkComparisonLE , mkComparisonLT , mkComparison , lookupComparison , listVarsWith , lookupName , lookupNames , validApps , findValidApp , preludeNameInstances ) where -- TODO: document and test functions of the Instances module import Data.Express.Basic import Data.Express.Name import Data.Express.Express import Data.Express.Utils.Typeable import Data.Express.Utils.List import Data.Maybe import Control.Applicative ((<$>)) -- for GHC <= 7.8 -- reifying instances -- -- | /O(1)./ -- Reifies an 'Eq' instance into a list of 'Expr's. -- The list will contain '==' and '/=' for the given type. -- (cf. 'mkEq', 'mkEquation') -- -- > > reifyEq (undefined :: Int) -- > [ (==) :: Int -> Int -> Bool -- > , (/=) :: Int -> Int -> Bool ] -- -- > > reifyEq (undefined :: Bool) -- > [ (==) :: Bool -> Bool -> Bool -- > , (/=) :: Bool -> Bool -> Bool ] -- -- > > reifyEq (undefined :: String) -- > [ (==) :: [Char] -> [Char] -> Bool -- > , (/=) :: [Char] -> [Char] -> Bool ] reifyEq :: (Typeable a, Eq a) => a -> [Expr] reifyEq a = mkEq ((==) -:> a) -- | /O(1)./ -- Reifies an 'Ord' instance into a list of 'Expr's. -- The list will contain 'compare', '<=' and '<' for the given type. -- (cf. 'mkOrd', 'mkOrdLessEqual', 'mkComparisonLE', 'mkComparisonLT') -- -- > > reifyOrd (undefined :: Int) -- > [ (<=) :: Int -> Int -> Bool -- > , (<) :: Int -> Int -> Bool ] -- -- > > reifyOrd (undefined :: Bool) -- > [ (<=) :: Bool -> Bool -> Bool -- > , (<) :: Bool -> Bool -> Bool ] -- -- > > reifyOrd (undefined :: [Bool]) -- > [ (<=) :: [Bool] -> [Bool] -> Bool -- > , (<) :: [Bool] -> [Bool] -> Bool ] reifyOrd :: (Typeable a, Ord a) => a -> [Expr] reifyOrd a = mkOrd (compare -:> a) -- | /O(1)./ -- Reifies 'Eq' and 'Ord' instances into a list of 'Expr'. reifyEqOrd :: (Typeable a, Ord a) => a -> [Expr] reifyEqOrd a = reifyEq a ++ reifyOrd a -- | /O(1)./ -- Reifies a 'Name' instance into a list of 'Expr's. -- The list will contain 'name' for the given type. -- (cf. 'mkName', 'lookupName', 'lookupNames') -- -- > > reifyName (undefined :: Int) -- > [name :: Int -> [Char]] -- -- > > reifyName (undefined :: Bool) -- > [name :: Bool -> [Char]] reifyName :: (Typeable a, Name a) => a -> [Expr] reifyName a = mkName (name -:> a) -- todo: reifyExpr and related functions -- | /O(1)/. -- Builds a reified 'Eq' instance from the given '==' function. -- (cf. 'reifyEq') -- -- > > mkEq ((==) :: Int -> Int -> Bool) -- > [ (==) :: Int -> Int -> Bool -- > , (/=) :: Int -> Int -> Bool ] mkEq :: Typeable a => (a -> a -> Bool) -> [Expr] mkEq (==) = [ value "==" (==) , value "/=" (/=) ] where x /= y = not (x == y) -- | /O(1)/. -- Builds a reified 'Ord' instance from the given 'compare' function. -- (cf. 'reifyOrd', 'mkOrdLessEqual') mkOrd :: Typeable a => (a -> a -> Ordering) -> [Expr] mkOrd compare = [ value "<=" (<=) , value "<" (<) -- we don't include other Ord functions, at least for now --, value "compare" compare ] where x < y = x `compare` y == LT x <= y = x `compare` y /= GT -- | /O(1)/. -- Builds a reified 'Ord' instance from the given '<=' function. -- (cf. 'reifyOrd', 'mkOrd') mkOrdLessEqual :: Typeable a => (a -> a -> Bool) -> [Expr] mkOrdLessEqual (<=) = [ value "<=" (<=) , value "<" (<) ] where x < y = not (y <= x) -- | /O(1)/. -- Builds a reified 'Name' instance from the given 'name' function. -- (cf. 'reifyName', 'mkNameWith') mkName :: Typeable a => (a -> String) -> [Expr] mkName name = [value "name" name] -- | /O(1)/. -- Builds a reified 'Name' instance from the given 'String' and type. -- (cf. 'reifyName', 'mkName') mkNameWith :: Typeable a => String -> a -> [Expr] mkNameWith n a = [value "name" (const n -:> a)] -- searching for functions -- lookupComparison :: String -> TypeRep -> [Expr] -> Maybe Expr lookupComparison n' t = find (\i@(Value n _) -> n == n' && typ i == mkComparisonTy t) -- | /O(n)./ -- Returns whether an 'Eq' instance exists in the given instances list -- for the given 'TypeRep'. -- -- > > isEqT (reifyEqOrd (undefined :: Int)) (typeOf (undefined :: Int)) -- > True -- -- > > isEqT (reifyEqOrd (undefined :: Int)) (typeOf (undefined :: [[[Int]]])) -- > False -- -- Given that the instances list has length /n/, this function is /O(n)/. isEqT :: [Expr] -> TypeRep -> Bool isEqT is t = isJust $ lookupComparison "==" t is -- | /O(n)./ -- Returns whether an 'Ord' instance exists in the given instances list -- for the given 'TypeRep'. -- -- > > isOrdT (reifyEqOrd (undefined :: Int)) (typeOf (undefined :: Int)) -- > True -- -- > > isOrdT (reifyEqOrd (undefined :: Int)) (typeOf (undefined :: [[[Int]]])) -- > False -- -- Given that the instances list has length /n/, this function is /O(n)/. isOrdT :: [Expr] -> TypeRep -> Bool isOrdT is t = isJust $ lookupComparison "<=" t is -- | /O(n)./ -- Returns whether both 'Eq' and 'Ord' instance exist in the given list -- for the given 'TypeRep'. -- -- Given that the instances list has length /n/, this function is /O(n)/. isEqOrdT :: [Expr] -> TypeRep -> Bool isEqOrdT is t = isEqT is t && isOrdT is t -- | /O(n+m)./ -- Returns whether an 'Eq' instance exists in the given instances list -- for the given 'Expr'. -- -- > > isEq (reifyEqOrd (undefined :: Int)) (val (0::Int)) -- > True -- -- > > isEq (reifyEqOrd (undefined :: Int)) (val ([[[0::Int]]])) -- > False -- -- Given that the instances list has length /m/ -- and that the given 'Expr' has size /n/, -- this function is /O(n+m)/. isEq :: [Expr] -> Expr -> Bool isEq is = isEqT is . typ -- | /O(n+m)./ -- Returns whether an 'Ord' instance exists in the given instances list -- for the given 'Expr'. -- -- > > isOrd (reifyEqOrd (undefined :: Int)) (val (0::Int)) -- > True -- -- > > isOrd (reifyEqOrd (undefined :: Int)) (val ([[[0::Int]]])) -- > False -- -- Given that the instances list has length /m/ -- and that the given 'Expr' has size /n/, -- this function is /O(n+m)/. isOrd :: [Expr] -> Expr -> Bool isOrd is = isOrdT is . typ -- | /O(n+m)./ -- Returns whether both 'Eq' and 'Ord' instance exist in the given list -- for the given 'Expr'. -- -- Given that the instances list has length /m/ -- and that the given 'Expr' has size /n/, -- this function is /O(n+m)/. isEqOrd :: [Expr] -> Expr -> Bool isEqOrd is e = isEq is e && isOrd is e mkComparison :: String -> [Expr] -> Expr -> Expr -> Expr mkComparison n' is e1 e2 = fromMaybe (val False) $ do e1e <- findValidApp os e1 e1e $$ e2 where os = [eq | eq@(Value n _) <- is, n == n'] mkEquation :: [Expr] -> Expr -> Expr -> Expr mkEquation = mkComparison "==" mkComparisonLT :: [Expr] -> Expr -> Expr -> Expr mkComparisonLT = mkComparison "<" mkComparisonLE :: [Expr] -> Expr -> Expr -> Expr mkComparisonLE = mkComparison "<=" lookupName :: [Expr] -> Expr -> String lookupName is e = fromMaybe "x" $ eval "x" <$> findValidApp es e where es = [e | e@(Value "name" _) <- is] lookupNames :: [Expr] -> Expr -> [String] lookupNames is = variableNamesFromTemplate . lookupName is listVarsWith :: [Expr] -> Expr -> [Expr] listVarsWith is e = lookupName is e `listVarsAsTypeOf` e -- helpers -- validApps :: [Expr] -> Expr -> [Expr] validApps es e = mapMaybe ($$ e) es findValidApp :: [Expr] -> Expr -> Maybe Expr findValidApp es = listToMaybe . validApps es (-:>) :: (a -> b) -> a -> (a -> b) (-:>) = const infixl 1 -:> -- reified instances -- preludeNameInstances :: [Expr] preludeNameInstances = concat [ reifyName (u :: ()) , reifyName (u :: Bool) , reifyName (u :: Int) , reifyName (u :: Integer) , reifyName (u :: Char) , reifyName (u :: Ordering) , reifyName (u :: Rational) , reifyName (u :: Float) , reifyName (u :: Double) , reifyName (u :: [()]) , reifyName (u :: [Bool]) , reifyName (u :: [Int]) , reifyName (u :: [Integer]) , reifyName (u :: [Char]) , reifyName (u :: [Ordering]) , reifyName (u :: [Rational]) , reifyName (u :: [Float]) , reifyName (u :: [Double]) , reifyName (u :: Maybe ()) , reifyName (u :: Maybe Bool) , reifyName (u :: Maybe Int) , reifyName (u :: Maybe Integer) , reifyName (u :: Maybe Char) , reifyName (u :: Maybe Ordering) , reifyName (u :: Maybe Rational) , reifyName (u :: Maybe Float) , reifyName (u :: Maybe Double) , reifyName (u :: ((),())) , reifyName (u :: (Bool,Bool)) , reifyName (u :: (Int,Int)) , reifyName (u :: (Integer,Integer)) , reifyName (u :: (Char,Char)) , reifyName (u :: (Ordering,Ordering)) , reifyName (u :: (Rational,Rational)) , reifyName (u :: (Float,Float)) , reifyName (u :: (Double,Double)) , reifyName (u :: () -> ()) , reifyName (u :: Bool -> Bool) , reifyName (u :: Int -> Int) , reifyName (u :: Integer -> Integer) , reifyName (u :: Char -> Char) , reifyName (u :: Ordering -> Ordering) , reifyName (u :: Rational -> Rational) , reifyName (u :: Float -> Float) , reifyName (u :: Double -> Double) , reifyName (u :: () -> () -> ()) , reifyName (u :: Bool -> Bool -> Bool) , reifyName (u :: Int -> Int -> Int) , reifyName (u :: Integer -> Integer -> Integer) , reifyName (u :: Char -> Char -> Char) , reifyName (u :: Ordering -> Ordering -> Ordering) , reifyName (u :: Rational -> Rational -> Rational) , reifyName (u :: Float -> Float -> Float) , reifyName (u :: Double -> Double -> Double) ] where u :: a u = undefined