{-# LANGUAGE DeriveFunctor , DeriveGeneric , FlexibleContexts , FlexibleInstances , FunctionalDependencies , KindSignatures , MultiParamTypeClasses , ScopedTypeVariables , TypeOperators , TypeSynonymInstances , UndecidableInstances #-} {-| Module : Data.Filter Description : Utilities for filtering Copyright : (c) Sophie Hirn, 2018 License : BSD2 Maintainer : sophie.hirn@wyvernscale.com Some helpers to make using Prelude.filter and similar value selection a bit easier. Includes combinators for predicates as well as an operator to match the constructor used for the given value. -} module Data.Filter ( -- * Constructors -- | The @(=?=)@-operator can be used to check whether two values were -- generated using the same constructor. For this to work, the -- underlying data type must instantiate 'GHC.Generics.Generic', -- parameters to the constructor can additionally be left out if their -- type implements 'ReduceWith'. constrName , HasConstructor (..) -- * Reduction -- | Constructors can be /reduced/ to values by passing them arbitrary -- arguments. The actual value of those does not impact the result of -- the @(=?=)@-operator. For lazy members, passing 'undefined' works -- just fine, but putting 'undefined' into strict fields causes carnage. -- 'ReduceWith' provides arbitrary values, deriving from `Default` where -- possible. , ReduceWith (..) , Reduce (..) -- * Operators , (=?=) , (==>) , (<||>) , any_ , (<&&>) , all_ -- * Matching Wrappers , Infinite (..) -- * Useful functions from other modules , mapMaybe ) where import Control.Monad import Data.Default import Data.List import Data.Maybe import GHC.Generics -- | Retrieve the constructor name of the given value as a string. This -- implementation is taken from https://stackoverflow.com/questions/48179380/getting-the-data-constructor-name-as-a-string-using-ghc-generics . constrName :: (HasConstructor (Rep a), Generic a) => a -> String constrName = genericConstrName . from -- | Automatically derived from 'Generic' instances. class HasConstructor (f :: * -> *) where genericConstrName :: f x -> String instance HasConstructor f => HasConstructor (D1 c f) where genericConstrName (M1 x) = genericConstrName x instance (HasConstructor x, HasConstructor y) => HasConstructor (x :+: y) where genericConstrName (L1 l) = genericConstrName l genericConstrName (R1 r) = genericConstrName r instance Constructor c => HasConstructor (C1 c f) where genericConstrName x = conName x -- end from -- | Type that can be reduced away from a constructor. Use this to make your -- data types compatible. The reduction process and the @(=?=)@-operator do -- not evaluate fields, therefore creating an empty instance which defaults to -- @'reduceWith' = 'undefined'@ is okay __as long as no reduced field of__ -- __this type is strict__. class ReduceWith a where reduceWith :: a reduceWith = undefined instance {-# OVERLAPPING #-} ReduceWith Bool where reduceWith = True instance {-# OVERLAPPING #-} ReduceWith Char where reduceWith = ' ' instance {-# OVERLAPPABLE #-} (Default a) => ReduceWith a where reduceWith = def -- | Reduction of a constructor @a -> ... -> c@ to a value of type @c@. class (HasConstructor (Rep c), Generic c) => Reduce a c | a -> c where reduce :: a -> c instance {-# OVERLAPPABLE #-} (HasConstructor (Rep a), Generic a) => Reduce a a where reduce = id instance {-# OVERLAPPABLE #-} (ReduceWith a, Reduce b c) => Reduce (a -> b) c where reduce = reduce . ($ reduceWith) -- | Checks whether two values are created by the same data constructor. Also -- works with constructors that have not yet received all their arguments. -- This allows for very convenient constructs, e.g.: -- -- >>> Just 1 =?= Just -- True -- -- >>> Just 1 =?= Nothing -- False -- -- >>> let filterJust = filter (=?= Just) -- >>> filterJust [Just 1, Nothing, Just 9001] -- [Just 1, Just 9001] -- -- >>> let filterJust_ = mapMaybe $ (=?= Just) ==> fromJust -- >>> filterJust_ [Just 1, Nothing, Just 9001] -- [1, 9001] -- -- >>> let over9000 = mapMaybe $ ((=?= Just) <&&> (>9000) . fromJust) ==> fromJust -- >>> over9000 [Just 1, Nothing, Just 9001] -- [9001] (=?=) :: (Reduce a c, Reduce b c) => a -> b -> Bool infixl 4 =?= (=?=) a b = constrName (reduce a) == constrName (reduce b) -- | @(pred ==> f) x@ returns @'Just' (f x)@ if @pred x@ succeeds and -- @'Nothing'@ otherwise. (==>) :: (a -> Bool) -> (a -> b) -> a -> Maybe b (==>) p f x = if p x then Just $ f x else Nothing (<||>) :: (a -> Bool) -> (a -> Bool) -> a -> Bool infixl 2 <||> (<||>) = liftM2 (||) any_ :: [a -> Bool] -> a -> Bool any_ = foldl' (<||>) $ const False (<&&>) :: (a -> Bool) -> (a -> Bool) -> a -> Bool infixl 3 <&&> (<&&>) = liftM2 (&&) all_ :: [a -> Bool] -> a -> Bool all_ = foldl' (<&&>) $ const True -- | Adds negative and positive infinity to an ordered type. The 'fromEnum' -- function is inherently susceptible to overflow since the class 'Enum' is -- defined using 'Int' instead of 'Integer', but this should not cause trouble -- with \"small\" enums. data Infinite a = NegInfin | Exact !a | PosInfin deriving (Eq, Functor, Read, Show, Ord, Generic) instance (Eq a, Bounded a, Enum a) => Enum (Infinite a) where fromEnum NegInfin = fromEnum (minBound :: a) - 1 fromEnum (Exact x) = fromEnum x fromEnum PosInfin = fromEnum (maxBound :: a) + 1 toEnum x | x == fromEnum (minBound :: a) - 1 = NegInfin | x == fromEnum (maxBound :: a) + 1 = PosInfin | otherwise = Exact $ toEnum x succ NegInfin = Exact minBound succ PosInfin = PosInfin succ (Exact x) | x == maxBound = PosInfin | otherwise = Exact $ succ x pred NegInfin = NegInfin pred PosInfin = Exact maxBound pred (Exact x) | x == minBound = NegInfin | otherwise = Exact $ pred x instance (Default a) => Default (Infinite a) where def = Exact def