enumerate 0.0.0 → 0.1.0
raw patch · 14 files changed
+1442/−1488 lines, 14 filesdep −modular-arithmeticdep ~semigroupssetup-changed
Dependencies removed: modular-arithmetic
Dependency ranges changed: semigroups
Files
- LICENSE +20/−20
- Main.hs +2/−2
- README.md +25/−6
- Setup.hs +2/−2
- enumerate.cabal +63/−66
- sources/Data/Enumerate.hs +23/−23
- sources/Data/Enumerate/Domain.hs +0/−109
- sources/Data/Enumerate/Example.hs +51/−52
- sources/Data/Enumerate/Extra.hs +144/−132
- sources/Data/Enumerate/Function.hs +236/−231
- sources/Data/Enumerate/Large.hs +26/−26
- sources/Data/Enumerate/Map.hs +312/−302
- sources/Data/Enumerate/Reify.hs +157/−157
- sources/Data/Enumerate/Types.hs +381/−360
LICENSE view
@@ -1,20 +1,20 @@-Copyright (c) 2015 Sam Boosalis--Permission is hereby granted, free of charge, to any person obtaining-a copy of this software and associated documentation files (the-"Software"), to deal in the Software without restriction, including-without limitation the rights to use, copy, modify, merge, publish,-distribute, sublicense, and/or sell copies of the Software, and to-permit persons to whom the Software is furnished to do so, subject to-the following conditions:--The above copyright notice and this permission notice shall be included-in all copies or substantial portions of the Software.--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.+Copyright (c) 2015 Sam Boosalis + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Main.hs view
@@ -1,2 +1,2 @@-{-# OPTIONS_GHC -fno-warn-missing-signatures #-}-import Data.Enumerate.Example+{-# OPTIONS_GHC -fno-warn-missing-signatures #-} +import Data.Enumerate.Example
README.md view
@@ -1,6 +1,25 @@-# enumerate-enumerate all the values in a finite type (automatically) --## (extensive) documentation-https://hackage.haskell.org/package/enumerate-+# enumerate +enumerate all the values in a finite type (automatically) + +provides (1) a typeclass for enumerating all values in a finite type, +(2) a generic instance for automatic deriving, and +(3) helpers that reify functions (partial or total, monadic or pure) into a Map. + +# example + +```haskell + {-# LANGUAGE DeriveGeneric, DeriveAnyClass #-} + import Data.Enumerable (Enumerable(..)) + import Data.Generics (Generics) + + data CrudOp = Add | Edit | Delete | View + deriving (Eq,Ord,Enum,Bounded,Generic,Enumerable) + data Route = Home | Person CrudOp | House CrudOp + deriving (Eq,Ord,Generic,Enumerable) + + >>> enumerated :: [Route] + [Home, Person Add, Person Edit, Person Delete, Person View, House Add, House Edit, House Delete, House View] +``` + +# (extensive) documentation: +https://hackage.haskell.org/package/enumerate
Setup.hs view
@@ -1,2 +1,2 @@-import Distribution.Simple-main = defaultMain+import Distribution.Simple +main = defaultMain
enumerate.cabal view
@@ -1,66 +1,63 @@--- Initial enumerate.cabal generated by cabal init. For further --- documentation, see http://haskell.org/cabal/users-guide/--name: enumerate-version: 0.0.0-synopsis: enumerate all the values in a finite type (automatically)-description: provides a typeclass, a generic instance for automatic deriving, and helpers that reify functions (partial or total, monadic or pure) into a Map -homepage: https://github.com/sboosali/enumerate-license: MIT-license-file: LICENSE-author: Sam Boosalis-maintainer: samboosalis@gmail.com--- copyright: -category: Data-build-type: Simple-extra-source-files: README.md-cabal-version: >=1.10-tested-with: GHC ==7.10.2--source-repository head- type: git- location: git://github.com/sboosali/enumerate.git--library- exposed-modules: - Data.Enumerate- Data.Enumerate.Types - Data.Enumerate.Reify - Data.Enumerate.Domain- Data.Enumerate.Example- Data.Enumerate.Map- Data.Enumerate.Extra- Data.Enumerate.Large - Data.Enumerate.Function- -- Data.CoRec- -- Data.CoRec.MemoTrie-- build-depends: - base >=4.8 && <4.9- , containers ==0.5.* - , ghc-prim==0.4.*-- , semigroups ==0.16.*- , exceptions ==0.8.*- , MemoTrie ==0.6.*--- , spoon ==0.3.*- , deepseq ==1.4.*-- , vinyl==0.5.* - , modular-arithmetic==1.2.* - , template-haskell ==2.10.*-- hs-source-dirs: sources- default-language: Haskell2010---executable example -- main-is: Main.hs - hs-source-dirs: . - default-language: Haskell2010-- build-depends: - base >=4.8 && <4.9- , enumerate ==0.0.0 -+-- Initial enumerate.cabal generated by cabal init. For further +-- documentation, see http://haskell.org/cabal/users-guide/ + +name: enumerate +version: 0.1.0 +synopsis: enumerate all the values in a finite type (automatically) +description: provides (1) a typeclass for enumerating all values in a finite type, (2) a generic instance for automatic deriving, and (3) helpers that reify functions (partial or total, monadic or pure) into a Map. + +homepage: https://github.com/sboosali/enumerate +license: MIT +license-file: LICENSE +author: Sam Boosalis +maintainer: samboosalis@gmail.com +-- copyright: +category: Data +build-type: Simple +extra-source-files: README.md +cabal-version: >=1.10 + + +library + exposed-modules: + Data.Enumerate + Data.Enumerate.Types + Data.Enumerate.Reify + -- Data.Enumerate.Domain + Data.Enumerate.Example + Data.Enumerate.Map + Data.Enumerate.Extra + Data.Enumerate.Large + Data.Enumerate.Function + -- Data.CoRec + -- Data.CoRec.MemoTrie + -- Data.TEnumerate + + build-depends: + base ==4.8.* + , containers ==0.5.* + , ghc-prim==0.4.* + + , semigroups ==0.18.* + , exceptions ==0.8.* + , MemoTrie ==0.6.* +-- , spoon ==0.3.* + , deepseq ==1.4.* + + , vinyl==0.5.* + -- , modular-arithmetic==1.2.* + , template-haskell ==2.10.* + + hs-source-dirs: sources + default-language: Haskell2010 + + +executable example + + main-is: Main.hs + hs-source-dirs: . + default-language: Haskell2010 + + build-depends: + base >=4.8 && <4.9 + , enumerate ==0.0.0
sources/Data/Enumerate.hs view
@@ -1,23 +1,23 @@-{-| see "Data.Enumerable.Types" for detailed documentation. --to import every symbol in this package, run this in GHCi:--@-:m + Data.Enumerate Data.Enumerate.Extra Data.Enumerate.Large Data.Enumerate.Function -@--the modules "Data.Enumerate.Large" and "Data.Enumerate.Function" have orphan instances for large types, -and aren't reexported by default. -this makes attempting to enumerate them a type error, rather than runtime non-termination. ---}-module Data.Enumerate- ( module Data.Enumerate.Types- , module Data.Enumerate.Reify- -- , module Data.Enumerate.Domain - , module Data.Enumerate.Map - ) where -import Data.Enumerate.Types-import Data.Enumerate.Reify--- import Data.Enumerate.Domain-import Data.Enumerate.Map+{-| see "Data.Enumerable.Types" for detailed documentation. + +to import every symbol in this package, run this in GHCi: + +@ +:m + Data.Enumerate Data.Enumerate.Extra Data.Enumerate.Large Data.Enumerate.Function +@ + +the modules "Data.Enumerate.Large" and "Data.Enumerate.Function" have orphan instances for large types, +and aren't reexported by default. +this makes attempting to enumerate them a type error, rather than runtime non-termination. + +-} +module Data.Enumerate + ( module Data.Enumerate.Types + , module Data.Enumerate.Reify + -- , module Data.Enumerate.Domain + , module Data.Enumerate.Map + ) where +import Data.Enumerate.Types +import Data.Enumerate.Reify +-- import Data.Enumerate.Domain +import Data.Enumerate.Map
− sources/Data/Enumerate/Domain.hs
@@ -1,109 +0,0 @@-{-# LANGUAGE DeriveGeneric, DefaultSignatures, TypeOperators, FlexibleInstances, FlexibleContexts, DeriveAnyClass, TypeFamilies, LambdaCase, EmptyCase, MultiParamTypeClasses #-}--{- |--e.g. generate a boilerplate 'HasTrie' instance for a @Context@ type that has both sum types and product types --@-data Context- = GlobalContext | EmacsContext BufferName BufferMode BufferContents | OtherContext String- deriving (Show,Generic) - -- Strings are infinite, so Context is too, that's okay --instance HasDomain Context --type BufferName = String-type BufferMode = String-type BufferContents = String--instance HasTrie Context where- type a :->: b = ContextTrie ('TrieGeneric' a b)- trie = 'trieGeneric'- untrie = 'untrieGeneric'-@--the conversions (between a type @a@ and its representation @(Rep a x)@) make the generic instance -slower than a manual instance.--but, if the function you're memoizing is costly enough, and the datatype it consumes is messy enough, -the four-line @HasTrie@ instance (and a one line call to 'memo') can save both runtime and developer time.----}-module Data.Enumerate.Domain where -import Data.CoRec--import Data.MemoTrie--import GHC.Generics----- class GHasTrie f a where--- type GTrie f a :: (* -> *)--- -- type GTrie f :: (* -> *) -> * -> * -> (* -> *)--- gtrie :: f p -> (a -> ) -> (GTrie f a) p--- guntrie :: (GTrie f a) p -> (a -> )----- -- | zero cases become (one case with) zero fields--- instance GHasTrie (V1) a where--- type GTrie (V1) a = (U1)--- gtrie _ = (U1)----- -- | one case becomes (one case with) one field--- instance GHasTrie (U1) a where--- type GTrie (U1) a = (K1 () a)--- -- gtrie (U1) = (K1 a)--- gtrie (U1) = undefined -- --- -- | call 'trie'--- instance (HasTrie a) => GHasTrie (K1 i a) where--- type GTrie (K1 i a) = Trie a --- gtrie (K1 a) = trie a ----- -- -- | two cases become (one case with) two fields--- -- instance (GHasTrie (f), GHasTrie (g)) => GHasTrie (f :+: g) where--- -- type GTrie (f :+: g) = Either (GTrie f) (GTrie g) --- -- gtrie (L1 f) = Left (gtrie f) --- -- gtrie (R1 g) = Right (gtrie g) ----- -- -- | two fields become (one case with) (one field of) two arrows--- -- instance (GHasDomain (f), GHasDomain (g)) => GHasDomain (f :*: g) where--- -- type GDomain (f :*: g) = ((GDomain f), (GDomain g))--- -- gdomain (f :*: g) = (gdomain f, gdomain g)----- -- -- | (ignore metadata) --- -- instance (GHasDomain (f)) => GHasDomain (M1 i a f) where--- -- type GDomain (M1 i a f) = (GDomain f)--- -- gdomain (M1 f) = gdomain f----- {- | ---- e.g. ---- @--- a ~ Context---- Rep a ~ ---- GDomain (Rep a) ~ ---- GCanonical (GDomain (Rep a)) ~ ---- @---- -}--- -- type TrieGeneric a = GCanonical (GDomain (Rep a))---- -- trieGeneric :: --- -- trieGeneric = ---- -- untrieGeneric :: --- -- untrieGeneric = -
sources/Data/Enumerate/Example.hs view
@@ -1,52 +1,51 @@-{-# LANGUAGE LambdaCase, DeriveGeneric, DeriveAnyClass #-}-module Data.Enumerate.Example where -import Data.Enumerate --import System.Environment (getArgs)-import Data.Void (Void)-import GHC.Generics (Generic) ---main = mainWith =<< getArgs--mainWith = \case- _ -> return() ---{- | (for documentation) --demonstrates: empty type, unit type, product type, sum type, type variable.--with @\{\-\# LANGUAGE DeriveGeneric, DeriveAnyClass \#\-\}@, the derivation is a one-liner: --@-data DemoEnumerable a = ... deriving (Show,Generic,Enumerable) -@---}-data DemoEnumerable a- = DemoEnumerable0 Void- | DemoEnumerable1- | DemoEnumerable2 Bool (Maybe Bool) - | DemoEnumerable3 a- deriving (Show,Generic,Enumerable) --{- | (for documentation) --@demoEnumerated = enumerated@-->>> traverse print demoEnumerated-DemoEnumerable1-DemoEnumerable2 False Nothing-DemoEnumerable2 False (Just False)-DemoEnumerable2 False (Just True)-DemoEnumerable2 True Nothing-DemoEnumerable2 True (Just False)-DemoEnumerable2 True (Just True)-DemoEnumerable3 False-DemoEnumerable3 True---}-demoEnumerated :: [DemoEnumerable Bool] -demoEnumerated = enumerated-+{-# LANGUAGE LambdaCase, DeriveGeneric, DeriveAnyClass #-} +module Data.Enumerate.Example where +import Data.Enumerate + +import System.Environment (getArgs) +import Data.Void (Void) +import GHC.Generics (Generic) + + +main = mainWith =<< getArgs + +mainWith = \case + _ -> traverse print demoEnumerated + + +{- | (for documentation) + +demonstrates: empty type, unit type, product type, sum type, type variable. + +with @\{\-\# LANGUAGE DeriveGeneric, DeriveAnyClass \#\-\}@, the derivation is a one-liner: + +@ +data DemoEnumerable a = ... deriving (Show,Generic,Enumerable) +@ + +-} +data DemoEnumerable a + = DemoEnumerable0 Void + | DemoEnumerable1 + | DemoEnumerable2 Bool (Maybe Bool) + | DemoEnumerable3 a + deriving (Show,Generic,Enumerable) + +{- | (for documentation) + +@demoEnumerated = enumerated@ + +>>> traverse print demoEnumerated +DemoEnumerable1 +DemoEnumerable2 False Nothing +DemoEnumerable2 False (Just False) +DemoEnumerable2 False (Just True) +DemoEnumerable2 True Nothing +DemoEnumerable2 True (Just False) +DemoEnumerable2 True (Just True) +DemoEnumerable3 False +DemoEnumerable3 True + +-} +demoEnumerated :: [DemoEnumerable Bool] +demoEnumerated = enumerated
sources/Data/Enumerate/Extra.hs view
@@ -1,132 +1,144 @@-{-# LANGUAGE RankNTypes, LambdaCase, ScopedTypeVariables #-}-{-| ---}-module Data.Enumerate.Extra where--import Control.Monad.Catch (MonadThrow(..), SomeException(..))-import Control.DeepSeq (NFData(..), deepseq) ---- import Language.Haskell.TH.Syntax (Name,nameBase)-import Control.Arrow ((&&&), (>>>))-import System.IO.Unsafe (unsafePerformIO) -import Control.Exception (catches, throwIO, Handler(..), AsyncException, ArithException, ArrayException, ErrorCall, PatternMatchFail)-import Data.Foldable (traverse_)-import Numeric.Natural -import qualified Data.Set as Set-import Data.Set (Set) -import qualified Data.List as List -import qualified Data.Ord as Ord---{-| @failed = 'throwM' . 'userError'@---}-failed :: (MonadThrow m) => String -> m a-failed = throwM . userError---- | generalize a function that fails with @Nothing@. -maybe2throw :: (a -> Maybe b) -> (forall m. MonadThrow m => a -> m b) -maybe2throw f = f >>> \case- Nothing -> failed "Nothing"- Just x -> return x ---- | generalize a function that fails with @[]@. -list2throw :: (a -> [b]) -> (forall m. MonadThrow m => a -> m b)-list2throw f = f >>> \case- [] -> failed "[]"-- (x:_) -> return x---- | generalize a function that fails with @Left@. -either2throw :: (a -> Either SomeException b) -> (forall m. MonadThrow m => a -> m b)-either2throw f = f >>> \case- Left e -> throwM e- Right x -> return x --{-| makes an *unsafely*-partial function (i.e. a function that throws exceptions or that has inexhaustive pattern matching) into a *safely*-partial function (i.e. that explicitly returns in a monad that supports failure).----}-totalizeFunction :: (NFData b, MonadThrow m) => (a -> b) -> (a -> m b)-totalizeFunction f = g - where g x = spoonWith defaultPartialityHandlers (f x)--{-| handles the following exceptions: --* 'ArithException'-* 'ArrayException'-* 'ErrorCall'-* 'PatternMatchFail' ---}-defaultPartialityHandlers :: (MonadThrow m) => [Handler (m a)]-defaultPartialityHandlers =- [ Handler $ \(e :: AsyncException) -> throwIO e -- TODO I hope they are tried in order - , Handler $ \(e :: ArithException) -> return (throwM e)- , Handler $ \(e :: ArrayException) -> return (throwM e)- , Handler $ \(e :: ErrorCall) -> return (throwM e)- , Handler $ \(e :: PatternMatchFail) -> return (throwM e)- , Handler $ \(e :: SomeException) -> return (throwM e)- ]-{-# INLINEABLE defaultPartialityHandlers #-}--{-| Evaluate a value to normal form and 'throwM' any exceptions are thrown during evaluation. For any error-free value, @spoon = Just@.--taken from the <https://hackage.haskell.org/package/spoon-0.3.1/docs/Control-Spoon.html spoon> package. ---}-spoonWith :: (NFData a, MonadThrow m) => [Handler (m a)] -> a -> m a -spoonWith handlers a = unsafePerformIO $ do - deepseq a (return `fmap` return a) `catches` handlers -{-# INLINEABLE spoonWith #-}--{- | the eliminator as a function and the introducer as a string--helper for declaring Show instances of datatypes without visible constructors (like @Map@-which is shown as an list).---}--showsPrecWith :: (Show a, Show b) => String -> (a -> b) -> Int -> a -> ShowS-showsPrecWith stringFrom functionInto p x = showParen (p > 10) $- showString stringFrom . showString " " . shows (functionInto x)---- showsPrecWith :: (Show a, Show b) => Name -> (a -> b) -> Int -> a -> ShowS--- showsPrecWith nameFrom functionInto p x = showParen (p > 10) $--- showString (nameBase nameFrom) . showString " " . shows (functionInto x)--int2natural :: Int -> Natural -int2natural = fromInteger . toInteger--{-| the power set of a set of values. -->>> (powerset2matrix . powerSet . Set.fromList) [1..3]-[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]]---}-powerSet :: (Ord a) => Set a -> Set (Set a) -powerSet values = Set.singleton values `Set.union` _Set_bind powerSet (dropEach values) - where - _Set_bind :: (Ord a, Ord b) => (a -> Set b) -> Set a -> Set b - _Set_bind f = _Set_join . Set.map f - _Set_join :: (Ord a) => Set (Set a) -> Set a- _Set_join = Set.unions . Set.toList --{-| >>> (powerset2matrix . dropEach . Set.fromList) [1..3]-[[1,2],[1,3],[2,3]]---}-dropEach :: (Ord a) => Set a -> Set (Set a) -dropEach values = Set.map dropOne values - where- dropOne value = Set.delete value values --{-| convert a power set to an isomorphic matrix, sorting the entries. --(for doctest) ---}-powerset2matrix :: Set (Set a) -> [[a]] -powerset2matrix = (List.sortBy (Ord.comparing length) . fmap Set.toList . Set.toList)-+{-# LANGUAGE RankNTypes, LambdaCase, ScopedTypeVariables #-} +{-| + +-} +module Data.Enumerate.Extra where + +import Control.Monad.Catch (MonadThrow(..), SomeException(..)) +import Control.DeepSeq (NFData(..), deepseq) + +-- import Language.Haskell.TH.Syntax (Name,nameBase) +import Control.Arrow ((&&&), (>>>)) +import System.IO.Unsafe (unsafePerformIO) +import Control.Exception (catches, throwIO, Handler(..), AsyncException, ArithException, ArrayException, ErrorCall, PatternMatchFail) +import Data.Foldable (traverse_) +import Numeric.Natural +import qualified Data.Set as Set +import Data.Set (Set) +import qualified Data.List as List +import qualified Data.Ord as Ord + + +{-| @failed = 'throwM' . 'userError'@ + +-} +failed :: (MonadThrow m) => String -> m a +failed = throwM . userError + +-- | generalize a function that fails with @Nothing@. +maybe2throw :: (a -> Maybe b) -> (forall m. MonadThrow m => a -> m b) +maybe2throw f = f >>> \case + Nothing -> failed "Nothing" + Just x -> return x + +-- | generalize a function that fails with @[]@. +list2throw :: (a -> [b]) -> (forall m. MonadThrow m => a -> m b) +list2throw f = f >>> \case + [] -> failed "[]" + + (x:_) -> return x + +-- | generalize a function that fails with @Left@. +either2throw :: (a -> Either SomeException b) -> (forall m. MonadThrow m => a -> m b) +either2throw f = f >>> \case + Left e -> throwM e + Right x -> return x + +{-| specialization -} +throw2maybe :: (forall m. MonadThrow m => a -> m b) -> (a -> Maybe b) +throw2maybe = id + +{-| specialization -} +throw2either :: (forall m. MonadThrow m => a -> m b) -> (a -> Either SomeException b) +throw2either = id + +{-| specialization -} +throw2list :: (forall m. MonadThrow m => a -> m b) -> (a -> [b]) +throw2list = id + +{-| makes an *unsafely*-partial function (i.e. a function that throws exceptions or that has inexhaustive pattern matching) into a *safely*-partial function (i.e. that explicitly returns in a monad that supports failure). + + +-} +totalizeFunction :: (NFData b, MonadThrow m) => (a -> b) -> (a -> m b) +totalizeFunction f = g + where g x = spoonWith defaultPartialityHandlers (f x) + +{-| handles the following exceptions: + +* 'ArithException' +* 'ArrayException' +* 'ErrorCall' +* 'PatternMatchFail' + +-} +defaultPartialityHandlers :: (MonadThrow m) => [Handler (m a)] +defaultPartialityHandlers = + [ Handler $ \(e :: AsyncException) -> throwIO e -- TODO I hope they are tried in order + , Handler $ \(e :: ArithException) -> return (throwM e) + , Handler $ \(e :: ArrayException) -> return (throwM e) + , Handler $ \(e :: ErrorCall) -> return (throwM e) + , Handler $ \(e :: PatternMatchFail) -> return (throwM e) + , Handler $ \(e :: SomeException) -> return (throwM e) + ] +{-# INLINEABLE defaultPartialityHandlers #-} + +{-| Evaluate a value to normal form and 'throwM' any exceptions are thrown during evaluation. For any error-free value, @spoon = Just@. + +taken from the <https://hackage.haskell.org/package/spoon-0.3.1/docs/Control-Spoon.html spoon> package. + +-} +spoonWith :: (NFData a, MonadThrow m) => [Handler (m a)] -> a -> m a +spoonWith handlers a = unsafePerformIO $ do + deepseq a (return `fmap` return a) `catches` handlers +{-# INLINEABLE spoonWith #-} + +{- | the eliminator as a function and the introducer as a string + +helper for declaring Show instances of datatypes without visible constructors (like @Map@ +which is shown as an list). + +-} + +showsPrecWith :: (Show a, Show b) => String -> (a -> b) -> Int -> a -> ShowS +showsPrecWith stringFrom functionInto p x = showParen (p > 10) $ + showString stringFrom . showString " " . shows (functionInto x) + +-- showsPrecWith :: (Show a, Show b) => Name -> (a -> b) -> Int -> a -> ShowS +-- showsPrecWith nameFrom functionInto p x = showParen (p > 10) $ +-- showString (nameBase nameFrom) . showString " " . shows (functionInto x) + +int2natural :: Int -> Natural +int2natural = fromInteger . toInteger + +{-| the power set of a set of values. + +>>> (powerset2matrix . powerSet . Set.fromList) [1..3] +[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]] + +-} +powerSet :: (Ord a) => Set a -> Set (Set a) +powerSet values = Set.singleton values `Set.union` _Set_bind powerSet (dropEach values) + where + _Set_bind :: (Ord a, Ord b) => (a -> Set b) -> Set a -> Set b + _Set_bind f = _Set_join . Set.map f + _Set_join :: (Ord a) => Set (Set a) -> Set a + _Set_join = Set.unions . Set.toList + +{-| >>> (powerset2matrix . dropEach . Set.fromList) [1..3] +[[1,2],[1,3],[2,3]] + +-} +dropEach :: (Ord a) => Set a -> Set (Set a) +dropEach values = Set.map dropOne values + where + dropOne value = Set.delete value values + +{-| convert a power set to an isomorphic matrix, sorting the entries. + +(for doctest) + +-} +powerset2matrix :: Set (Set a) -> [[a]] +powerset2matrix = (List.sortBy (Ord.comparing length) . fmap Set.toList . Set.toList) +
sources/Data/Enumerate/Function.hs view
@@ -1,231 +1,236 @@-{-# LANGUAGE TupleSections, ScopedTypeVariables #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-| orphan instances, of 'Enumerate'/'Eq'/'Show', for functions. --(that are included for completeness, but not exported by default (i.e. by "Data.Enumerate"). -you probably want build-time instance-resolution errors rather than possible runtime non-termination). ---@-- doctest@-->>> :set -XLambdaCase ->>> let printMappings mappings = traverse (\mapping -> (putStrLn"") >> (traverse print) mapping) mappings >> return() ---}-module Data.Enumerate.Function where-import Data.Enumerate.Types -import Data.Enumerate.Reify -import Data.Enumerate.Map-import Data.Enumerate.Extra --import Data.Proxy-import qualified Data.Map as Map---{-| the exponential type. --the 'cardinality' is the cardinality of @b@ raised to the cardinality @a@, i.e. @|b|^|a|@.--warning: it grows very quickly. --might be useful for generating random functions on small types, -like to fuzz test type class laws. --the 'cardinality' call is efficient (depending on the efficiency of the base type's call). -you should be able to safely (WRT performance) call 'enumerateBelow', -unless the arithmetic itself becomes too expensive. --@enumerated = 'functionEnumerated'@ ---}-instance (Enumerable a, Enumerable b, Ord a, Ord b) => Enumerable (a -> b) where - enumerated = functionEnumerated - cardinality _ = cardinality (Proxy :: Proxy b) ^ cardinality (Proxy :: Proxy a) ---{-| brute-force function extensionality. --warning: the size of the domain grows exponentially in the number of arguments. -->>> (\case LT -> False; EQ -> False; GT -> False) == const False -True->>> (\case LT -> False; EQ -> False; GT -> False) == const True -False--because functions are curried, the instance is recursive, and it works on functions of any arity: --> -- De Morgan's laws->>> (\x y -> not (x && y)) == (\x y -> not x || not y)-True->>> (\x y -> not (x || y)) == (\x y -> not x && not y)-True---}-instance (Enumerable a, Eq b) => Eq (a -> b) where- f == g = all ((==) <$> f <*> g) enumerated- f /= g = any ((/=) <$> f <*> g) enumerated---{-| -->>> print not -unsafeFromList [(False,True),(True,False)]--because functions are curried, the instance is recursive, and it works on functions of any arity: -->>> print (&&) -unsafeFromList [(False,unsafeFromList [(False,False),(True,False)]),(True,unsafeFromList [(False,False),(True,True)])]---}-instance (Enumerable a, Show a, Show b) => Show (a -> b) where- showsPrec = showsPrecWith "unsafeFromList" reifyFunction---{-| wraps 'Map.lookup' -->>> (unsafeFromList [(False,True),(True,False)]) False -True->>> (unsafeFromList [(False,True),(True,False)]) True -False---}-unsafeFromList :: (Ord a) => [(a,b)] -> (a -> b)-unsafeFromList l = unsafeToFunction (Map.fromList l) -{-# INLINABLE unsafeFromList #-}--functionEnumerated :: (Enumerable a, Enumerable b, Ord a, Ord b) => [a -> b]-functionEnumerated = functions - where- functions = (unsafeToFunction . Map.fromList) <$> mappings - mappings = mappingEnumeratedAt enumerated enumerated---{-| @[(a,b)]@ is a mapping, @[[(a,b)]]@ is a list of mappings. -->>> let orderingPredicates = mappingEnumeratedAt [LT,EQ,GT] [False,True] ->>> print $ length orderingPredicates -8->>> printMappings $ orderingPredicates -<BLANKLINE>-(LT,False)-(EQ,False)-(GT,False)-<BLANKLINE>-(LT,False)-(EQ,False)-(GT,True)-<BLANKLINE>-(LT,False)-(EQ,True)-(GT,False)-<BLANKLINE>-(LT,False)-(EQ,True)-(GT,True)-<BLANKLINE>-(LT,True)-(EQ,False)-(GT,False)-<BLANKLINE>-(LT,True)-(EQ,False)-(GT,True)-<BLANKLINE>-(LT,True)-(EQ,True)-(GT,False)-<BLANKLINE>-(LT,True)-(EQ,True)-(GT,True)-(LT,False)-(EQ,False)-(GT,False)-<BLANKLINE>-(LT,False)-(EQ,False)-(GT,True)-<BLANKLINE>-(LT,False)-(EQ,True)-(GT,False)-<BLANKLINE>-(LT,False)-(EQ,True)-(GT,True)-<BLANKLINE>-(LT,True)-(EQ,False)-(GT,False)-<BLANKLINE>-(LT,True)-(EQ,False)-(GT,True)-<BLANKLINE>-(LT,True)-(EQ,True)-(GT,False)-<BLANKLINE>-(LT,True)-(EQ,True)-(GT,True)--where the (total) mapping:--@-(LT,False)-(EQ,False)-(GT,True)-@--is equivalent to the function:--@-\\case- LT -> False- EQ -> False- GT -> True-@----}-mappingEnumeratedAt :: [a] -> [b] -> [[(a,b)]] -- TODO diagonalize? performance? -mappingEnumeratedAt as bs = go (crossProduct as bs)- where- go [] = [] - go [somePairs] = do- pair <- somePairs - return$ [pair]- go (somePairs:theProduct) = do- pair <- somePairs - theExponent <- go theProduct - return$ pair : theExponent --{-| -->>> let crossOrderingBoolean = crossProduct [LT,EQ,GT] [False,True]->>> printMappings $ crossOrderingBoolean ->>> -(LT,False)-(LT,True)-<BLANKLINE>-(EQ,False)-(EQ,True)-<BLANKLINE>-(GT,False)-(GT,True)--the length of the outer list is the size of the first set and -the length of the inner list is the size of the second set. -->>> print $ length crossOrderingBoolean-3->>> print $ length (head crossOrderingBoolean)-2---}-crossProduct :: [a] -> [b] -> [[(a,b)]] -crossProduct [] _ = [] -crossProduct (aValue:theDomain) theCodomain =- fmap (aValue,) theCodomain : crossProduct theDomain theCodomain-+{-# LANGUAGE TupleSections, ScopedTypeVariables #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-| orphan instances, of 'Enumerate'\/'Eq'\/'Show', for functions. + +(that are included for completeness, but not exported by default (i.e. by "Data.Enumerate"). +you probably want build-time instance-resolution errors rather than possible runtime non-termination). + + +@-- doctest@ + +>>> :set -XLambdaCase +>>> let printMappings mappings = traverse (\mapping -> (putStrLn"") >> (traverse print) mapping) mappings >> return() + +-} +module Data.Enumerate.Function where +import Data.Enumerate.Types +import Data.Enumerate.Reify +import Data.Enumerate.Map +import Data.Enumerate.Extra + +import Data.Proxy +import qualified Data.Map as Map + + +{-| the exponential type. + +the 'cardinality' is the cardinality of @b@ raised to the cardinality @a@, i.e. @|b|^|a|@. + +warning: it grows very quickly. + +might be useful for generating random functions on small types, +like to fuzz test type class laws. + +the 'cardinality' call is efficient (depending on the efficiency of the base type's call). +you should be able to safely (WRT performance) call 'enumerateBelow', +unless the arithmetic itself becomes too expensive. + +@ +instance ('Enumerable' a, Enumerable b, 'Ord' a, Ord b) => Enumerable (a -> b) where + enumerated = 'functionEnumerated' +@ + +-} +instance (Enumerable a, Enumerable b, Ord a, Ord b) => Enumerable (a -> b) where + enumerated = functionEnumerated + cardinality _ = cardinality (Proxy :: Proxy b) ^ cardinality (Proxy :: Proxy a) + + +{-| brute-force function extensionality. + +warning: the size of the domain grows exponentially in the number of arguments. + +>>> (\case LT -> False; EQ -> False; GT -> False) == const False +True +>>> (\case LT -> False; EQ -> False; GT -> False) == const True +False + +because functions are curried, the instance is recursive, and it works on functions of any arity: + +> -- De Morgan's laws +>>> (\x y -> not (x && y)) == (\x y -> not x || not y) +True +>>> (\x y -> not (x || y)) == (\x y -> not x && not y) +True + +-} +instance (Enumerable a, Eq b) => Eq (a -> b) where + f == g = all ((==) <$> f <*> g) enumerated + f /= g = any ((/=) <$> f <*> g) enumerated + + +{-| + +>>> print not +unsafeFromList [(False,True),(True,False)] + +because functions are curried, the instance is recursive, and it works on functions of any arity: + +>>> print (&&) +unsafeFromList [(False,unsafeFromList [(False,False),(True,False)]),(True,unsafeFromList [(False,False),(True,True)])] + +-} +instance (Enumerable a, Show a, Show b) => Show (a -> b) where + showsPrec = showsPrecWith "unsafeFromList" reifyFunction + + +{-| wraps 'Map.lookup' + +>>> (unsafeFromList [(False,True),(True,False)]) False +True +>>> (unsafeFromList [(False,True),(True,False)]) True +False + +-} +unsafeFromList :: (Ord a) => [(a,b)] -> (a -> b) +unsafeFromList l = unsafeToFunction (Map.fromList l) +{-# INLINABLE unsafeFromList #-} + +{-| see 'mappingEnumeratedAt' -} +functionEnumerated :: (Enumerable a, Enumerable b, Ord a, Ord b) => [a -> b] +functionEnumerated = functions + where + functions = (unsafeToFunction . Map.fromList) <$> mappings + mappings = mappingEnumeratedAt enumerated enumerated + + +{-| @[(a,b)]@ is a mapping, @[[(a,b)]]@ is a list of mappings. + +>>> let orderingPredicates = mappingEnumeratedAt [LT,EQ,GT] [False,True] +>>> print $ length orderingPredicates +8 +>>> printMappings $ orderingPredicates +<BLANKLINE> +(LT,False) +(EQ,False) +(GT,False) +<BLANKLINE> +(LT,False) +(EQ,False) +(GT,True) +<BLANKLINE> +(LT,False) +(EQ,True) +(GT,False) +<BLANKLINE> +(LT,False) +(EQ,True) +(GT,True) +<BLANKLINE> +(LT,True) +(EQ,False) +(GT,False) +<BLANKLINE> +(LT,True) +(EQ,False) +(GT,True) +<BLANKLINE> +(LT,True) +(EQ,True) +(GT,False) +<BLANKLINE> +(LT,True) +(EQ,True) +(GT,True) +<BLANKLINE> +(LT,False) +(EQ,False) +(GT,False) +<BLANKLINE> +(LT,False) +(EQ,False) +(GT,True) +<BLANKLINE> +(LT,False) +(EQ,True) +(GT,False) +<BLANKLINE> +(LT,False) +(EQ,True) +(GT,True) +<BLANKLINE> +(LT,True) +(EQ,False) +(GT,False) +<BLANKLINE> +(LT,True) +(EQ,False) +(GT,True) +<BLANKLINE> +(LT,True) +(EQ,True) +(GT,False) +<BLANKLINE> +(LT,True) +(EQ,True) +(GT,True) + +where the (total) mapping: + +@ +(LT,False) +(EQ,False) +(GT,True) +@ + +is equivalent to the function: + +@ +\\case + LT -> False + EQ -> False + GT -> True +@ + + +-} +mappingEnumeratedAt :: [a] -> [b] -> [[(a,b)]] -- TODO diagonalize? performance? +mappingEnumeratedAt as bs = go (crossProduct as bs) + where + go [] = [] + go [somePairs] = do + pair <- somePairs + return$ [pair] + go (somePairs:theProduct) = do + pair <- somePairs + theExponent <- go theProduct + return$ pair : theExponent + +{-| + +>>> let crossOrderingBoolean = crossProduct [LT,EQ,GT] [False,True] +>>> printMappings $ crossOrderingBoolean +>>> +(LT,False) +(LT,True) +<BLANKLINE> +(EQ,False) +(EQ,True) +<BLANKLINE> +(GT,False) +(GT,True) + +the length of the outer list is the size of the first set and +the length of the inner list is the size of the second set. + +>>> print $ length crossOrderingBoolean +3 +>>> print $ length (head crossOrderingBoolean) +2 + +-} +crossProduct :: [a] -> [b] -> [[(a,b)]] +crossProduct [] _ = [] +crossProduct (aValue:theDomain) theCodomain = + fmap (aValue,) theCodomain : crossProduct theDomain theCodomain +
sources/Data/Enumerate/Large.hs view
@@ -1,26 +1,26 @@-{-# LANGUAGE TupleSections #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}-{-| orphan instances, of 'Enumerate', for large types (i.e. @Word32@ / @Word64@ / @Int32@ / @Int64@).--(that are included for completeness, but not exported by default (i.e. by "Data.Enumerate"). -you probably want build-time instance-resolution errors rather than probable runtime non-termination). ---}-module Data.Enumerate.Large where-import Data.Enumerate.Types --import Data.Word (Word32, Word64)-import Data.Int (Int32, Int64)---instance Enumerable Int32 where enumerated = boundedEnumerated; cardinality = boundedCardinality -instance Enumerable Word32 where enumerated = boundedEnumerated; cardinality = boundedCardinality --{-| finite but too big. @2^64@ is over a billion billion (@1,000,000,000,000@). --e.g. 'Enumerate.reifyFunction' (which takes time linear in the domain) on a function of type @(:: Int -> Bool)@, even a lazy one, won't terminate anytime soon. ---}-instance Enumerable Int64 where enumerated = boundedEnumerated; cardinality = boundedCardinality -instance Enumerable Word64 where enumerated = boundedEnumerated; cardinality = boundedCardinality -+{-# LANGUAGE TupleSections #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} +{-| orphan instances, of 'Enumerate', for large types (i.e. 'Word32' \/ 'Word64' \/ 'Int32' \/ 'Int64'). + +(that are included for completeness, but not exported by default (i.e. by "Data.Enumerate"). +you probably want build-time instance-resolution errors rather than probable runtime non-termination). + +-} +module Data.Enumerate.Large where +import Data.Enumerate.Types + +import Data.Word (Word32, Word64) +import Data.Int (Int32, Int64) + + +instance Enumerable Int32 where enumerated = boundedEnumerated; cardinality = boundedCardinality +instance Enumerable Word32 where enumerated = boundedEnumerated; cardinality = boundedCardinality + +{-| finite but too big. @2^64@ is over a billion billion (@1,000,000,000,000@). + +e.g. 'Enumerate.reifyFunction' (which takes time linear in the domain) on a function of type @(:: Int -> Bool)@, even a lazy one, won't terminate anytime soon. + +-} +instance Enumerable Int64 where enumerated = boundedEnumerated; cardinality = boundedCardinality +instance Enumerable Word64 where enumerated = boundedEnumerated; cardinality = boundedCardinality +
sources/Data/Enumerate/Map.hs view
@@ -1,302 +1,312 @@-{-# LANGUAGE RankNTypes, LambdaCase #-}-{-| converting between partial functions and maps. --@-- doctest@-->>> :set +m->>> :set -XLambdaCase ->>> :{-let uppercasePartial :: (MonadThrow m) => Char -> m Char -- Partial Char Char - uppercasePartial = \case- 'a' -> return 'A'- 'b' -> return 'B'- 'z' -> return 'Z'- _ -> failed "uppercasePartial"-:}--a (safely-)partial function is isomorphic with a @Map@: --@-'fromFunctionM' . 'toFunctionM' = 'id' -'toFunctionM' . 'fromFunctionM' = 'id'-@--modulo the error thrown. ---}-module Data.Enumerate.Map where-import Data.Enumerate.Extra -import Data.Enumerate.Types-import Data.Enumerate.Reify --import Control.Monad.Catch (MonadThrow(..))-import Data.List.NonEmpty (NonEmpty(..))-import qualified Data.List.NonEmpty as NonEmpty-import Data.Semigroup ((<>))--import qualified Data.Map as Map-import Data.Map (Map)-import qualified Data.Set as Set-import Data.Set (Set)-import Data.Maybe (fromJust, mapMaybe, listToMaybe) -import Control.Exception(PatternMatchFail(..)) --{- | convert a total function to a map. --@->>> fromFunction 'not' -fromList [(False,True),(True,False)]-@---}-fromFunction :: (Enumerable a, Ord a) => (a -> b) -> Map a b-fromFunction f = fromFunctionM (return.f) -{-# INLINABLE fromFunction #-}--{- | convert a (safely-)partial function to a map. --wraps 'reifyFunctionM'. ---}-fromFunctionM :: (Enumerable a, Ord a) => (Partial a b) -> Map a b-fromFunctionM f = Map.fromList (reifyFunctionM f)-{-# INLINABLE fromFunctionM #-}--{- | convert a map to a function, if the map is total. --@->>> let Just not' = toFunction (Map.fromList [(False,True),(True,False)])->>> not' False -True -@---} -toFunction :: (Enumerable a, Ord a) => Map a b -> Maybe (a -> b)-toFunction m = if isMapTotal m then Just f else Nothing - where f = unsafeToFunction m -- the fromJust is safe when the map is total -{-# INLINABLE toFunction #-}--{- | convert a (safely-)partial function to a map. --lookup failures are 'throwM'n as a 'PatternMatchFail'. --@->>> let idPartial = toFunctionM (Map.fromList [(True,True)])->>> idPartial True-True->>> idPartial False -*** Exception: toFunctionM-@---} -toFunctionM :: (Enumerable a, Ord a) => Map a b -> (Partial a b)-toFunctionM m = f - where- f x = maybe (throwM (PatternMatchFail "toFunctionM")) return (Map.lookup x m)-{-# INLINABLE toFunctionM #-}--{-| wraps 'Map.lookup' ---}-unsafeToFunction :: (Ord a) => Map a b -> (a -> b)-unsafeToFunction m x = fromJust (Map.lookup x m)-{-# INLINABLE unsafeToFunction #-}--{-| does the map contain every key in its domain? -->>> isMapTotal (Map.fromList [(False,True),(True,False)]) -True -->>> isMapTotal (Map.fromList [('a',0)]) -False ---}-isMapTotal :: (Enumerable a, Ord a) => Map a b -> Bool-isMapTotal m = all (\x -> Map.member x m) enumerated --{-| safely invert any map. ---}-invertMap :: (Ord a, Ord b) => Map a b -> Map b (NonEmpty a) -invertMap m = Map.fromListWith (<>) [(b, a:|[]) | (a, b) <- Map.toList m]--{-| refines the partial function, if total.-->>> :{ -let myNotM :: Monad m => Bool -> m Bool- myNotM False = return True - myNotM True = return False -:} ->>> let Just myNot = isTotalM myNotM->>> myNot False -True---}-isTotalM :: (Enumerable a, Ord a) => (Partial a b) -> Maybe (a -> b) -isTotalM f = (toFunction) (fromFunctionM f)--{-| the <https://en.wikipedia.org/wiki/Partial_function#Basic_concepts domain> of a partial function -is the subset of the 'enumerated' input where it's defined. --i.e. when @x \`member\` (domainM f)@ then @fromJust (f x)@ is defined. -->>> domainM uppercasePartial-['a','b','z'] ---}-domainM :: (Enumerable a) => (Partial a b) -> [a] -domainM f = foldMap go enumerated- where- go a = case f a of - Nothing -> [] - Just{} -> [a]--{-| (right name?) --@corange _ = enumerated@ ---}-corange :: (Enumerable a) => (a -> b) -> [a] -corange _ = enumerated --{-| --@corangeM _ = enumerated@ ---}-corangeM :: (Enumerable a) => (Partial a b) -> [a] -corangeM _ = enumerated --{-| the image of a total function. --@imageM f = map f 'enumerated'@--includes duplicates. ---}-image :: (Enumerable a) => (a -> b) -> [b] -image f = map f enumerated--{-| the image (not the 'codomain') of a partial function. --@imageM f = mapMaybe f 'enumerated'@--includes duplicates. ---}-imageM :: (Enumerable a) => (Partial a b) -> [b] -imageM f = mapMaybe f enumerated--{-| the codomain of a function. it contains the 'image'. --@codomain _ = enumerated@ ---}-codomain :: (Enumerable b) => (a -> b) -> [b] -codomain _ = enumerated --codomainM :: (Enumerable b) => (Partial a b) -> [b] -codomainM _ = enumerated --{-| invert a total function.--@(invert f) b@ is: --* @[]@ wherever @f@ is not surjective -* @[y]@ wherever @f@ is uniquely defined -* @(_:_)@ wherever @f@ is not injective --@invert f = 'invertM' (return.f)@---}-invert :: (Enumerable a, Ord a, Ord b) => (a -> b) -> (b -> [a])-invert f = invertM (return.f) --{-| invert a partial function.--@(invertM f) b@ is: --* @[]@ wherever @f@ is partial -* @[]@ wherever @f@ is not surjective -* @[y]@ wherever @f@ is uniquely defined -* @(_:_)@ wherever @f@ is not injective --a @Map@ is stored internally, with as many keys as the 'image' of @f@. --see also 'isBijectiveM'.---}-invertM :: (Enumerable a, Ord a, Ord b) => (Partial a b) -> (b -> [a])-invertM f = g- where- g b = maybe [] NonEmpty.toList (Map.lookup b m)- m = invertMap (fromFunctionM f) -- share the map --{-| ---}-getJectivityM :: (Enumerable a, Enumerable b, Ord a, Ord b) => (Partial a b) -> Maybe Jectivity -getJectivityM f- = case isBijectiveM f of -- TODO pick the right Monoid, whose append picks the first non-nothing - Just{} -> Just Bijective- Nothing -> case isInjectiveM f of- Just{} -> Just Injective - Nothing -> case isSurjectiveM f of- Just{} -> Just Surjective- Nothing -> Nothing ---{-| returns the inverse of the injection, if injective.--refines @(b -> [a])@ (i.e. the type of 'invertM') to @(b -> Maybe a)@. --unlike 'isBijectiveM', doesn't need an @(Enumerable b)@ constraint. this helps when you want to ensure a function into an infinite type (e.g. 'show') is injective. and still reasonably efficient, given the @(Ord b)@ constraint. ---}-isInjectiveM :: (Enumerable a, Ord a, Ord b) => (Partial a b) -> Maybe (b -> Maybe a)-isInjectiveM f = do -- TODO make it "correct by construction", rather than explicit validation - _bs <- isUnique (imageM f) -- Map.fromListWith (<>) [(b, a:|[]) | (a, b) <- Map.toList m]- return g - where- g = listToMaybe . invertM f--- can short-circuit. --{-| converts the list into a set, if it has no duplicates. ---}-isUnique :: (Ord a) => [a] -> Maybe (Set a) -isUnique l = if length l == length s then Nothing else Just s -- TODO make efficient, maybe single pass with Control.Foldl- where- s = Set.fromList l--{-| returns the inverse of the surjection, if surjective. -i.e. when a function's 'codomainM' equals its 'imageM'. --refines @(b -> [a])@ (i.e. the type of 'invertM') to @(b -> NonEmpty a)@. --can short-circuit. ---}-isSurjectiveM :: (Enumerable a, Enumerable b, Ord a, Ord b) => (Partial a b) -> Maybe (b -> NonEmpty a)-isSurjectiveM f = -- TODO make it "correct by construction", rather than explicit validation - if (Set.fromList (codomainM f)) `Set.isSubsetOf` (Set.fromList (imageM f)) -- the reverse always holds, no need to check - then Just g- else Nothing- where- g = NonEmpty.fromList . invertM f -- safe, by validation --{-| returns the inverse of the bijection, if bijective.--refines @(b -> [a])@ (i.e. the type of 'invertM') to @(b -> a)@. --can short-circuit. ---}-isBijectiveM :: (Enumerable a, Enumerable b, Ord a, Ord b) => (Partial a b) -> Maybe (b -> a)-isBijectiveM f = do - fIn <- isInjectiveM f- _fSur <- isSurjectiveM f -- TODO avoid re-computing invertM. isInjectiveWithM isSurjectiveWithM- let fBi = (fromJust . fIn) -- safe, because the intersection of "zero or one" with "one or more" is "one" - return fBi--- let fOp = invertMap (fromFunctionM f) -- share the map -+{-# LANGUAGE RankNTypes, LambdaCase #-} +{-| converting between partial functions and maps. + +@-- doctest@ + +>>> :set +m +>>> :set -XLambdaCase +>>> :{ +let uppercasePartial :: (MonadThrow m) => Char -> m Char -- Partial Char Char + uppercasePartial = \case + 'a' -> return 'A' + 'b' -> return 'B' + 'z' -> return 'Z' + _ -> failed "uppercasePartial" +:} + +a (safely-)partial function is isomorphic with a @Map@: + +@ +'fromFunctionM' . 'toFunctionM' = 'id' +'toFunctionM' . 'fromFunctionM' = 'id' +@ + +modulo the error thrown. + +-} +module Data.Enumerate.Map where +import Data.Enumerate.Extra +import Data.Enumerate.Types +import Data.Enumerate.Reify + +import Control.Monad.Catch (MonadThrow(..)) +import Data.List.NonEmpty (NonEmpty(..)) +import qualified Data.List.NonEmpty as NonEmpty +import Data.Semigroup ((<>)) + +import qualified Data.Map as Map +import Data.Map (Map) +import qualified Data.Set as Set +import Data.Set (Set) +import Data.Maybe (fromJust, mapMaybe, listToMaybe) +import Control.Exception(PatternMatchFail(..)) + +{- | convert a total function to a map. + +@ +>>> fromFunction 'not' +fromList [(False,True),(True,False)] +@ + +-} +fromFunction :: (Enumerable a, Ord a) => (a -> b) -> Map a b +fromFunction f = fromFunctionM (return.f) +{-# INLINABLE fromFunction #-} + +{- | convert a (safely-)partial function to a map. + +wraps 'reifyFunctionM'. + +-} +fromFunctionM :: (Enumerable a, Ord a) => (Partial a b) -> Map a b +fromFunctionM f = Map.fromList (reifyFunctionM f) +{-# INLINABLE fromFunctionM #-} + +{- | convert a map to a function, if the map is total. + +@ +>>> let Just not' = toFunction (Map.fromList [(False,True),(True,False)]) +>>> not' False +True +@ + +-} +toFunction :: (Enumerable a, Ord a) => Map a b -> Maybe (a -> b) +toFunction m = if isMapTotal m then Just f else Nothing + where f = unsafeToFunction m -- the fromJust is safe when the map is total +{-# INLINABLE toFunction #-} + +{- | convert a (safely-)partial function to a map. + +lookup failures are 'throwM'n as a 'PatternMatchFail'. + +@ +>>> let idPartial = toFunctionM (Map.fromList [(True,True)]) +>>> idPartial True +True +>>> idPartial False +*** Exception: toFunctionM +@ + +-} +toFunctionM :: (Enumerable a, Ord a) => Map a b -> (Partial a b) +toFunctionM m = f + where + f x = maybe (throwM (PatternMatchFail "toFunctionM")) return (Map.lookup x m) +{-# INLINABLE toFunctionM #-} + +{-| wraps 'Map.lookup' + +-} +unsafeToFunction :: (Ord a) => Map a b -> (a -> b) +unsafeToFunction m x = fromJust (Map.lookup x m) +{-# INLINABLE unsafeToFunction #-} + +{-| does the map contain every key in its domain? + +>>> isMapTotal (Map.fromList [(False,True),(True,False)]) +True + +>>> isMapTotal (Map.fromList [('a',0)]) +False + +-} +isMapTotal :: (Enumerable a, Ord a) => Map a b -> Bool +isMapTotal m = all (\x -> Map.member x m) enumerated + +{-| safely invert any map. + +-} +invertMap :: (Ord a, Ord b) => Map a b -> Map b (NonEmpty a) +invertMap m = Map.fromListWith (<>) [(b, a:|[]) | (a, b) <- Map.toList m] + +{-| refines the partial function, if total. + +>>> :{ +let myNotM :: Monad m => Bool -> m Bool + myNotM False = return True + myNotM True = return False +:} +>>> let Just myNot = isTotalM myNotM +>>> myNot False +True + +-} +isTotalM :: (Enumerable a, Ord a) => (Partial a b) -> Maybe (a -> b) +isTotalM f = (toFunction) (fromFunctionM f) + +{-| the <https://en.wikipedia.org/wiki/Partial_function#Basic_concepts domain> of a partial function +is the subset of the 'enumerated' input where it's defined. + +i.e. when @x \`member\` (domainM f)@ then @fromJust (f x)@ is defined. + +>>> domainM uppercasePartial +['a','b','z'] + +-} +domainM :: (Enumerable a) => (Partial a b) -> [a] +domainM f = foldMap go enumerated + where + go a = case f a of + Nothing -> [] + Just{} -> [a] + +{-| (right name?) + +@corange _ = enumerated@ + +-} +corange :: (Enumerable a) => (a -> b) -> [a] +corange _ = enumerated + +{-| + +@corangeM _ = enumerated@ + +-} +corangeM :: (Enumerable a) => (Partial a b) -> [a] +corangeM _ = enumerated + +{-| the image of a total function. + +@imageM f = map f 'enumerated'@ + +includes duplicates. + +-} +image :: (Enumerable a) => (a -> b) -> [b] +image f = map f enumerated + +{-| the image (not the 'codomain') of a partial function. + +@imageM f = mapMaybe f 'enumerated'@ + +includes duplicates. + +-} +imageM :: (Enumerable a) => (Partial a b) -> [b] +imageM f = mapMaybe f enumerated + +{-| the codomain of a function. it contains the 'image'. + +@codomain _ = enumerated@ + +-} +codomain :: (Enumerable b) => (a -> b) -> [b] +codomain _ = enumerated + +codomainM :: (Enumerable b) => (Partial a b) -> [b] +codomainM _ = enumerated + +{-| invert a total function. + +@(invert f) b@ is: + +* @[]@ wherever @f@ is not surjective +* @[y]@ wherever @f@ is uniquely defined +* @(_:_)@ wherever @f@ is not injective + +@invert f = 'invertM' (return.f)@ + +-} +invert :: (Enumerable a, Ord a, Ord b) => (a -> b) -> (b -> [a]) +invert f = invertM (return.f) + +{-| invert a partial function. + +@(invertM f) b@ is: + +* @[]@ wherever @f@ is partial +* @[]@ wherever @f@ is not surjective +* @[y]@ wherever @f@ is uniquely defined +* @(_:_)@ wherever @f@ is not injective + +a @Map@ is stored internally, with as many keys as the 'image' of @f@. + +see also 'isBijectiveM'. + +-} +invertM :: (Enumerable a, Ord a, Ord b) => (Partial a b) -> (b -> [a]) +invertM f = g + where + g b = maybe [] NonEmpty.toList (Map.lookup b m) + m = invertMap (fromFunctionM f) -- share the map + +{-| + +-} +getJectivityM :: (Enumerable a, Enumerable b, Ord a, Ord b) => (Partial a b) -> Maybe Jectivity +getJectivityM f + = case isBijectiveM f of -- TODO pick the right Monoid, whose append picks the first non-nothing + Just{} -> Just Bijective + Nothing -> case isInjectiveM f of + Just{} -> Just Injective + Nothing -> case isSurjectiveM f of + Just{} -> Just Surjective + Nothing -> Nothing + + +isInjective :: (Enumerable a, Ord a, Ord b) => (a -> b) -> Maybe (b -> Maybe a) +isInjective f = isInjectiveM (return.f) + +{-| returns the inverse of the injection, if injective. + +refines @(b -> [a])@ (i.e. the type of 'invertM') to @(b -> Maybe a)@. + +unlike 'isBijectiveM', doesn't need an @(Enumerable b)@ constraint. this helps when you want to ensure a function into an infinite type (e.g. 'show') is injective. and still reasonably efficient, given the @(Ord b)@ constraint. + +-} +isInjectiveM :: (Enumerable a, Ord a, Ord b) => (Partial a b) -> Maybe (b -> Maybe a) +isInjectiveM f = do -- TODO make it "correct by construction", rather than explicit validation + _bs <- isUnique (imageM f) -- Map.fromListWith (<>) [(b, a:|[]) | (a, b) <- Map.toList m] + return g + where + g = listToMaybe . invertM f +-- can short-circuit. + +{-| converts the list into a set, if it has no duplicates. + +-} +isUnique :: (Ord a) => [a] -> Maybe (Set a) +isUnique l = if length l == length s then Nothing else Just s -- TODO make efficient, maybe single pass with Control.Foldl + where + s = Set.fromList l + +isSurjective :: (Enumerable a, Enumerable b, Ord a, Ord b) => (a -> b) -> Maybe (b -> NonEmpty a) +isSurjective f = isSurjectiveM (return.f) + +{-| returns the inverse of the surjection, if surjective. +i.e. when a function's 'codomainM' equals its 'imageM'. + +refines @(b -> [a])@ (i.e. the type of 'invertM') to @(b -> NonEmpty a)@. + +can short-circuit. + +-} +isSurjectiveM :: (Enumerable a, Enumerable b, Ord a, Ord b) => (Partial a b) -> Maybe (b -> NonEmpty a) +isSurjectiveM f = -- TODO make it "correct by construction", rather than explicit validation + if (Set.fromList (codomainM f)) `Set.isSubsetOf` (Set.fromList (imageM f)) -- the reverse always holds, no need to check + then Just g + else Nothing + where + g = NonEmpty.fromList . invertM f -- safe, by validation + + +isBijective :: (Enumerable a, Enumerable b, Ord a, Ord b) => (a -> b) -> Maybe (b -> a) +isBijective f = isBijectiveM (return.f) + +{-| returns the inverse of the bijection, if bijective. + +refines @(b -> [a])@ (i.e. the type of 'invertM') to @(b -> a)@. + +can short-circuit. + +-} +isBijectiveM :: (Enumerable a, Enumerable b, Ord a, Ord b) => (Partial a b) -> Maybe (b -> a) +isBijectiveM f = do + fIn <- isInjectiveM f + _fSur <- isSurjectiveM f -- TODO avoid re-computing invertM. isInjectiveWithM isSurjectiveWithM + let fBi = (fromJust . fIn) -- safe, because the intersection of "zero or one" with "one or more" is "one" + return fBi +-- let fOp = invertMap (fromFunctionM f) -- share the map +
sources/Data/Enumerate/Reify.hs view
@@ -1,157 +1,157 @@-{-# LANGUAGE RankNTypes, LambdaCase #-}-{-| see 'reifyFunctionAtM'. --@-- doctest@-->>> :set +m---}-module Data.Enumerate.Reify where-import Data.Enumerate.Types -import Data.Enumerate.Extra --import Control.Monad.Catch (MonadThrow(..), SomeException(..)) -import Control.DeepSeq (NFData) --import Control.Arrow ((&&&))---{- | reify a total function. --@->>> reifyFunction 'not'-[(False,True),(True,False)]-@---} -reifyFunction :: (Enumerable a) => (a -> b) -> [(a,b)]-reifyFunction f = reifyFunctionM (return . f)-{-# INLINABLE reifyFunction #-}---- | reify a total function at any subset of the domain. -reifyFunctionAt :: [a] -> (a -> b) -> [(a,b)]-reifyFunctionAt domain f = reifyFunctionAtM domain (return . f)-{-# INLINABLE reifyFunctionAt #-}---- | reify a (safely-)partial function into a map (which is implicitly partial, where @Map.lookup@ is like @($)@.-reifyFunctionM :: (Enumerable a) => (forall m. MonadThrow m => a -> m b) -> [(a,b)]-reifyFunctionM = reifyFunctionAtM enumerated-{-# INLINABLE reifyFunctionM #-}--{- | reify a (safely-)partial function at any domain. --use the functions suffixed with @M@ when your function is explicitly partial, -i.e. of type @(forall m. MonadThrow m => a -> m b)@. -when inside a function arrow, like: --@-reifyFunctionAtM :: [a] -> (forall m. MonadThrow m => a -> m b) -> [(a,b)]-reifyFunctionAtM domain f = ... -@--the @Rank2@ type (and non-concrete types) means that @f@ can only use -parametric polymorphic functions, or the methods of the @MonadThrow@ class -(namely 'throwM'), or methods of @MonadThrow@ superclasses (namely 'return', et cetera). --'MonadThrow' is a class from the @exceptions@ package that generalizes failibility. -it has instances for @Maybe@, @Either@, @[]@, @IO@, and more. --use the functions suffixed with @At@ when your domain isn't 'Enumerable', -or when you want to restrict the domain.- -the most general function in this module.-->>> :{-let uppercasePartial :: (MonadThrow m) => Char -> m Char - uppercasePartial c = case c of- 'a' -> return 'A'- 'b' -> return 'B'- 'z' -> return 'Z'- _ -> failed "uppercasePartial"-:}--@->>> reifyFunctionAtM ['a'..'c'] uppercasePartial-[('a','A'),('b','B')] -@--if your function doesn't fail under 'MonadThrow', see: --* 'reifyFunctionAtMaybe'-* 'reifyFunctionAtList'-* 'reifyFunctionAtEither'---}-reifyFunctionAtM :: [a] -> (Partial a b) -> [(a,b)]--- reifyFunctionAtM :: (MonadThrow m) => [a] -> (a -> m b) -> m (Map a b)-reifyFunctionAtM domain f - = concatMap (bitraverse pure id)- . fmap (id &&& f)- $ domain- where- bitraverse f g (x,y) = (,) <$> f x <*> g y -- avoid bifunctors dependency---- | @reifyPredicateAt = 'flip' 'filter'@-reifyPredicateAt :: [a] -> (a -> Bool) -> [a]-reifyPredicateAt = flip filter--- reifyPredicateAtM domain p = map fst (reifyFunctionAtM domain f)--- where--- f x = if p x then return x else throwM (ErrorCall "False")---- MonadThrow Maybe --- (e ~ SomeException) => MonadThrow (Either e)--- MonadThrow [] ---- | reify a (safely-)partial function that fails specifically under @Maybe@. -reifyFunctionMaybeAt :: [a] -> (a -> Maybe b) -> [(a, b)]-reifyFunctionMaybeAt domain f = reifyFunctionAtM domain (maybe2throw f)-{-# INLINABLE reifyFunctionMaybeAt #-}---- | reify a (safely-)partial function that fails specifically under @[]@. -reifyFunctionListAt :: [a] -> (a -> [b]) -> [(a, b)]-reifyFunctionListAt domain f = reifyFunctionAtM domain (list2throw f)-{-# INLINABLE reifyFunctionListAt #-}---- | reify a (safely-)partial function that fails specifically under @Either SomeException@. -reifyFunctionEitherAt :: [a] -> (a -> Either SomeException b) -> [(a, b)]-reifyFunctionEitherAt domain f = reifyFunctionAtM domain (either2throw f)-{-# INLINABLE reifyFunctionEitherAt #-}--{-| reifies an *unsafely*-partial function (i.e. a function that throws exceptions or that has inexhaustive pattern matching).--forces the function to be strict.--@->>> import Data.Ratio (Ratio) ->>> fmap (1/) [0..3 :: Ratio Integer]-[*** Exception: Ratio has zero denominator->>> let (1/) = reciprocal ->>> reifyFunctionSpoonAt [0..3 :: Ratio Integer] reciprocal -[(1 % 1,1 % 1),(2 % 1,1 % 2),(3 % 1,1 % 3)]-@--normal caveats from violating purity (via @unsafePerformIO@) and from catchalls (via @(e :: SomeExceptions -> _)@) apply.---}-reifyFunctionSpoonAt :: (NFData b) => [a] -> (a -> b) -> [(a, b)]-reifyFunctionSpoonAt domain f = reifyFunctionMaybeAt domain (totalizeFunction f)---- | reify a binary total function-reifyFunction2 :: (Enumerable a, Enumerable b) => (a -> b -> c) -> [(a,[(b,c)])]-reifyFunction2 f = reifyFunction2At enumerated enumerated f-{-# INLINABLE reifyFunction2 #-}---- | reify a binary total function at some domain-reifyFunction2At :: [a] -> [b] -> (a -> b -> c) -> [(a,[(b,c)])]-reifyFunction2At as bs f = reifyFunction2AtM as bs (\x y -> pure (f x y))-{-# INLINABLE reifyFunction2At #-}---- | reify a binary (safely-)partial function-reifyFunction2M :: (Enumerable a, Enumerable b) => (forall m. MonadThrow m => a -> b -> m c) -> [(a,[(b,c)])]-reifyFunction2M f = reifyFunction2AtM enumerated enumerated f-{-# INLINABLE reifyFunction2M #-}---- | reify a binary (safely-)partial function at some domain -reifyFunction2AtM :: [a] -> [b] -> (forall m. MonadThrow m => a -> b -> m c) -> [(a,[(b,c)])]-reifyFunction2AtM as bs f = reifyFunctionAt as (\a -> reifyFunctionAtM bs (f a))-+{-# LANGUAGE RankNTypes, LambdaCase #-} +{-| see 'reifyFunctionAtM'. + +@-- doctest@ + +>>> :set +m + +-} +module Data.Enumerate.Reify where +import Data.Enumerate.Types +import Data.Enumerate.Extra + +import Control.Monad.Catch (MonadThrow(..), SomeException(..)) +import Control.DeepSeq (NFData) + +import Control.Arrow ((&&&)) + + +{- | reify a total function. + +@ +>>> reifyFunction 'not' +[(False,True),(True,False)] +@ + +-} +reifyFunction :: (Enumerable a) => (a -> b) -> [(a,b)] +reifyFunction f = reifyFunctionM (return . f) +{-# INLINABLE reifyFunction #-} + +-- | reify a total function at any subset of the domain. +reifyFunctionAt :: [a] -> (a -> b) -> [(a,b)] +reifyFunctionAt domain f = reifyFunctionAtM domain (return . f) +{-# INLINABLE reifyFunctionAt #-} + +-- | reify a (safely-)partial function into a map (which is implicitly partial, where @Map.lookup@ is like @($)@. +reifyFunctionM :: (Enumerable a) => (forall m. MonadThrow m => a -> m b) -> [(a,b)] +reifyFunctionM = reifyFunctionAtM enumerated +{-# INLINABLE reifyFunctionM #-} + +{- | reify a (safely-)partial function at any domain. + +use the functions suffixed with @M@ when your function is explicitly partial, +i.e. of type @(forall m. MonadThrow m => a -> m b)@. +when inside a function arrow, like: + +@ +reifyFunctionAtM :: [a] -> (forall m. MonadThrow m => a -> m b) -> [(a,b)] +reifyFunctionAtM domain f = ... +@ + +the @Rank2@ type (and non-concrete types) means that @f@ can only use +parametric polymorphic functions, or the methods of the @MonadThrow@ class +(namely 'throwM'), or methods of @MonadThrow@ superclasses (namely 'return', et cetera). + +'MonadThrow' is a class from the @exceptions@ package that generalizes failibility. +it has instances for @Maybe@, @Either@, @[]@, @IO@, and more. + +use the functions suffixed with @At@ when your domain isn't 'Enumerable', +or when you want to restrict the domain. + +the most general function in this module. + +>>> :{ +let uppercasePartial :: (MonadThrow m) => Char -> m Char + uppercasePartial c = case c of + 'a' -> return 'A' + 'b' -> return 'B' + 'z' -> return 'Z' + _ -> failed "uppercasePartial" +:} + +@ +>>> reifyFunctionAtM ['a'..'c'] uppercasePartial +[('a','A'),('b','B')] +@ + +if your function doesn't fail under 'MonadThrow', see: + +* 'reifyFunctionAtMaybe' +* 'reifyFunctionAtList' +* 'reifyFunctionAtEither' + +-} +reifyFunctionAtM :: [a] -> (Partial a b) -> [(a,b)] +-- reifyFunctionAtM :: (MonadThrow m) => [a] -> (a -> m b) -> m (Map a b) +reifyFunctionAtM domain f + = concatMap (bitraverse pure id) + . fmap (id &&& f) + $ domain + where + bitraverse f g (x,y) = (,) <$> f x <*> g y -- avoid bifunctors dependency + +-- | @reifyPredicateAt = 'flip' 'filter'@ +reifyPredicateAt :: [a] -> (a -> Bool) -> [a] +reifyPredicateAt = flip filter +-- reifyPredicateAtM domain p = map fst (reifyFunctionAtM domain f) +-- where +-- f x = if p x then return x else throwM (ErrorCall "False") + +-- MonadThrow Maybe +-- (e ~ SomeException) => MonadThrow (Either e) +-- MonadThrow [] + +-- | reify a (safely-)partial function that fails specifically under @Maybe@. +reifyFunctionMaybeAt :: [a] -> (a -> Maybe b) -> [(a, b)] +reifyFunctionMaybeAt domain f = reifyFunctionAtM domain (maybe2throw f) +{-# INLINABLE reifyFunctionMaybeAt #-} + +-- | reify a (safely-)partial function that fails specifically under @[]@. +reifyFunctionListAt :: [a] -> (a -> [b]) -> [(a, b)] +reifyFunctionListAt domain f = reifyFunctionAtM domain (list2throw f) +{-# INLINABLE reifyFunctionListAt #-} + +-- | reify a (safely-)partial function that fails specifically under @Either SomeException@. +reifyFunctionEitherAt :: [a] -> (a -> Either SomeException b) -> [(a, b)] +reifyFunctionEitherAt domain f = reifyFunctionAtM domain (either2throw f) +{-# INLINABLE reifyFunctionEitherAt #-} + +{-| reifies an *unsafely*-partial function (i.e. a function that throws exceptions or that has inexhaustive pattern matching). + +forces the function to be strict. + +@ +>>> import Data.Ratio (Ratio) +>>> fmap (1/) [0..3 :: Ratio Integer] +[*** Exception: Ratio has zero denominator +>>> let (1/) = reciprocal +>>> reifyFunctionSpoonAt [0..3 :: Ratio Integer] reciprocal +[(1 % 1,1 % 1),(2 % 1,1 % 2),(3 % 1,1 % 3)] +@ + +normal caveats from violating purity (via @unsafePerformIO@) and from catchalls (via @(e :: SomeExceptions -> _)@) apply. + +-} +reifyFunctionSpoonAt :: (NFData b) => [a] -> (a -> b) -> [(a, b)] +reifyFunctionSpoonAt domain f = reifyFunctionMaybeAt domain (totalizeFunction f) + +-- | reify a binary total function +reifyFunction2 :: (Enumerable a, Enumerable b) => (a -> b -> c) -> [(a,[(b,c)])] +reifyFunction2 f = reifyFunction2At enumerated enumerated f +{-# INLINABLE reifyFunction2 #-} + +-- | reify a binary total function at some domain +reifyFunction2At :: [a] -> [b] -> (a -> b -> c) -> [(a,[(b,c)])] +reifyFunction2At as bs f = reifyFunction2AtM as bs (\x y -> pure (f x y)) +{-# INLINABLE reifyFunction2At #-} + +-- | reify a binary (safely-)partial function +reifyFunction2M :: (Enumerable a, Enumerable b) => (forall m. MonadThrow m => a -> b -> m c) -> [(a,[(b,c)])] +reifyFunction2M f = reifyFunction2AtM enumerated enumerated f +{-# INLINABLE reifyFunction2M #-} + +-- | reify a binary (safely-)partial function at some domain +reifyFunction2AtM :: [a] -> [b] -> (forall m. MonadThrow m => a -> b -> m c) -> [(a,[(b,c)])] +reifyFunction2AtM as bs f = reifyFunctionAt as (\a -> reifyFunctionAtM bs (f a)) +
sources/Data/Enumerate/Types.hs view
@@ -1,360 +1,381 @@-{-# LANGUAGE RankNTypes, ScopedTypeVariables, DefaultSignatures, TypeOperators, FlexibleInstances, FlexibleContexts, LambdaCase #-}-{- | see the 'Enumerable' class for documentation. --see "Data.Enumerate.Example" for examples. --can also help automatically derive <https://hackage.haskell.org/package/QuickCheck/docs/Test-QuickCheck-Arbitrary.html @QuickCheck@> instances: --@-newtype SmallNatural = ... -instance Enumerable SmallNatural where ... -newtype SmallString = ... -instance Enumerable SmallString where ... -data T = C0 | C1 () Bool SmallNatural SmallString | C2 ... -instance Arbitrary T where arbitrary = elements 'enumerated' -@---background on @Generics@: --* <https://hackage.haskell.org/package/base-4.8.1.0/docs/GHC-Generics.html GHC.Generics>---related packages:--* <http://hackage.haskell.org/package/emgm-0.4/docs/Generics-EMGM-Functions-Enum.html emgm>. allows infinite lists (by convention). too heavyweight. --* <http://hackage.haskell.org/package/enumerable enumerable>. no @Generic@ instance. --* <https://hackage.haskell.org/package/testing-feat-0.4.0.2/docs/Test-Feat-Class.html#t:Enumerable testing-feat>. too heavyweight (testing framework). --* <https://hackage.haskell.org/package/smallcheck smallcheck> too heavyweight (testing framework). Series enumerates up to some depth and can enumerated infinitely-inhabited types. --* https://hackage.haskell.org/package/quickcheck quickcheck> too heavyweight (testing framework, randomness unnecessary).---}--module Data.Enumerate.Types where-import Data.Enumerate.Extra --import Data.Modular-import Control.Monad.Catch (MonadThrow(..))--import GHC.Generics-import Data.Proxy-import Control.Arrow ((&&&))-import Data.List (genericLength)-import Data.Void (Void)-import Data.Word (Word8, Word16)-import Data.Int (Int8, Int16)-import qualified Data.Set as Set-import Data.Set (Set) -import System.Timeout-import Control.DeepSeq (NFData,force)-import GHC.TypeLits-import Numeric.Natural-import Data.Ix---{- | enumerate the set of all values in a (finitely enumerable) type. enumerates depth first. --generalizes 'Enum's to any finite/discrete type. an Enumerable is either:--* an Enum-* a product of Enumerables-* a sum of Enumerables--can be implemented automatically via its 'Generic' instance.--laws:--* consistent:-- * @'cardinality' = 'length' 'enumerated'@-- so you can index the 'enumerated' with a nonnegative index below the 'cardinality'.--* distinct:-- * @(Eq a) => 'nub' 'enumerated' == 'enumerated'@--* complete:-- * @x `'elem'` 'enumerated'@--* coincides with @Bounded@ @Enum@s:-- * @('Enum' a, 'Bounded' a) => 'enumerated' == 'boundedEnumerated'@-- * @('Enum' a) => 'enumerated' == 'enumEnumerated'@--(@Bounded@ constraint elided for convenience, but relevant.)--("inputs" a type, outputs a list of values).---}-class Enumerable a where-- enumerated :: [a]-- default enumerated :: (Generic a, GEnumerable (Rep a)) => [a]- enumerated = to <$> genumerated-- cardinality :: proxy a -> Natural- cardinality _ = genericLength (enumerated :: [a]) - -- overrideable for performance, but don't lie!-- -- default cardinality :: (Generic a, GEnumerable (Rep a)) => proxy a -> Natural- -- cardinality _ = gcardinality (Proxy :: Proxy (Rep a))- -- TODO merge both methods into one that returns their pair--{-| a (safely-)partial function. i.e. a function that:--* fails only via the 'throwM' method of 'MonadThrow' -* succeeds only via the 'return' method of 'Monad' ----}-type Partial a b = (forall m. MonadThrow m => a -> m b)---- | "Generic Enumerable", lifted to unary type constructors.-class GEnumerable f where- genumerated :: [f x]- gcardinality :: proxy f -> Natural---- | empty list -instance GEnumerable (V1) where- genumerated = []- gcardinality _ = 0- {-# INLINE gcardinality #-}---- | singleton list -instance GEnumerable (U1) where- genumerated = [U1]- gcardinality _ = 1- {-# INLINE gcardinality #-}--{-| call 'enumerated' ---}-instance (Enumerable a) => GEnumerable (K1 R a) where- genumerated = K1 <$> enumerated- gcardinality _ = cardinality (Proxy :: Proxy a)- {-# INLINE gcardinality #-}---- | multiply lists with @concatMap@-instance (GEnumerable (f), GEnumerable (g)) => GEnumerable (f :*: g) where- genumerated = (:*:) <$> genumerated <*> genumerated- gcardinality _ = gcardinality (Proxy :: Proxy (f)) * gcardinality (Proxy :: Proxy (g))- {-# INLINE gcardinality #-}- --- | add lists with @(<>)@-instance (GEnumerable (f), GEnumerable (g)) => GEnumerable (f :+: g) where- genumerated = map L1 genumerated ++ map R1 genumerated - gcardinality _ = gcardinality (Proxy :: Proxy (f)) + gcardinality (Proxy :: Proxy (g))- {-# INLINE gcardinality #-}---- | ignore selector metadata-instance (GEnumerable (f)) => GEnumerable (M1 S t f) where- genumerated = M1 <$> genumerated- gcardinality _ = gcardinality (Proxy :: Proxy (f))- {-# INLINE gcardinality #-}---- | ignore constructor metadata-instance (GEnumerable (f)) => GEnumerable (M1 C t f) where- genumerated = M1 <$> genumerated- gcardinality _ = gcardinality (Proxy :: Proxy (f))- {-# INLINE gcardinality #-}---- | ignore datatype metadata-instance (GEnumerable (f)) => GEnumerable (M1 D t f) where- genumerated = M1 <$> genumerated- gcardinality _ = gcardinality (Proxy :: Proxy (f))- {-# INLINE gcardinality #-}--{-| see "Data.Enumerate.Reify.getJectivityM"---}-data Jectivity = Injective | Surjective | Bijective deriving (Show,Read,Eq,Ord,Enum,Bounded)--{-| wrap any @(Bounded a, Enum a)@ to be a @Enumerable@ via 'boundedEnumerated'. --(avoids @OverlappingInstances@). ---}-newtype WrappedBoundedEnum a = WrappedBoundedEnum { unwrapBoundedEnum :: a } --instance (Bounded a, Enum a) => Enumerable (WrappedBoundedEnum a) where - enumerated = WrappedBoundedEnum <$> boundedEnumerated- cardinality _ = boundedCardinality (Proxy :: Proxy a)---- base types -instance Enumerable Void-instance Enumerable ()-instance Enumerable Bool-instance Enumerable Ordering--{- | -->>> (maxBound::Int8) - (minBound::Int8)-256---}-instance Enumerable Int8 where enumerated = boundedEnumerated; cardinality = boundedCardinality -instance Enumerable Word8 where enumerated = boundedEnumerated; cardinality = boundedCardinality -{- | -->>> (maxBound::Int16) - (minBound::Int16) -65535---}-instance Enumerable Int16 where enumerated = boundedEnumerated; cardinality = boundedCardinality -instance Enumerable Word16 where enumerated = boundedEnumerated; cardinality = boundedCardinality -{- | there are only a million (1,114,112) characters. -->>> ord minBound-0-->>> ord maxBound-1114111-->>> length [chr 0..]-1114112---}-instance Enumerable Char where enumerated = boundedEnumerated; cardinality = boundedCardinality --{-| the sum type. --the 'cardinality' is the sum of the cardinalities of @a@ and @b@. ---}-instance (Enumerable a, Enumerable b) => Enumerable (Either a b) where - enumerated = (Left <$> enumerated) ++ (Right <$> enumerated) - cardinality _ = cardinality (Proxy :: Proxy a) + cardinality (Proxy :: Proxy b)-instance (Enumerable a) => Enumerable (Maybe a) where - enumerated = Nothing : (Just <$> enumerated)- cardinality _ = 1 + cardinality (Proxy :: Proxy a)--{-| the product type. --the 'cardinality' is the product of the cardinalities of @a@ and @b@. ---}-instance (Enumerable a, Enumerable b) => Enumerable (a, b) where - enumerated = (,) <$> enumerated <*> enumerated- cardinality _ = cardinality (Proxy :: Proxy a) * cardinality (Proxy :: Proxy b)--instance (Enumerable a, Enumerable b, Enumerable c) => Enumerable (a, b, c)-instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d) => Enumerable (a, b, c, d)-instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d, Enumerable e) => Enumerable (a, b, c, d, e)-instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d, Enumerable e, Enumerable f) => Enumerable (a, b, c, d, e, f)-instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d, Enumerable e, Enumerable f, Enumerable g) => Enumerable (a, b, c, d, e, f, g)--{-| --the 'cardinality' is the cardinality of the 'powerSet' of @a@, i.e. @2^|a|@. -warning: it grows quickly. don't try to take the power set of 'Char'! or even 'Word8'. --the 'cardinality' call is efficient (depending on the efficiency of the base type's call). -you should be able to safely call 'enumerateBelow', unless the arithmetic itself becomes too large. ----}-instance (Enumerable a, Ord a) => Enumerable (Set a) where - enumerated = (Set.toList . powerSet . Set.fromList) enumerated- cardinality _ = 2 ^ cardinality (Proxy :: Proxy a) ---- | (from the @modular-arithmetic@ package)-instance (Integral i, Num i, KnownNat n) => Enumerable (Mod i n) where- enumerated = toMod <$> [0 .. fromInteger (natVal (Proxy :: Proxy n) - 1)]- cardinality _ = fromInteger (natVal (Proxy :: Proxy n))--{- | for non-'Generic' Bounded Enums:--@-instance Enumerable _ where- 'enumerated' = boundedEnumerated- 'cardinality' = 'boundedCardinality' -@---}-boundedEnumerated :: (Bounded a, Enum a) => [a]-boundedEnumerated = enumFromTo minBound maxBound--{-| for non-'Generic' Bounded Enums. --behavior may be undefined when the cardinality of @a@ is larger than the cardinality of @Int@. this should be okay, as @Int@ is at least as big as @Int64@, which is at least as big as all the monomorphic types in @base@ that instantiate @Bounded@. you can double-check with:-->>> boundedCardinality (const(undefined::Int)) -- platform specific-18446744073709551616--@-- i.e. 1 + 9223372036854775807 - -9223372036854775808@--works with non-zero-based Enum instances, like @Int64@ or a custom @toEnum/fromEnum@. -assumes the enumeration's numbering is contiguous, e.g. if @fromEnum 0@ and @fromEnum 2@ -both exist, then @fromEnum 1@ should exist too. ---}-boundedCardinality :: forall proxy a. (Bounded a, Enum a) => proxy a -> Natural -boundedCardinality _ = fromInteger (1 + (toInteger (fromEnum (maxBound::a))) - (toInteger (fromEnum (minBound::a))))--{- | for non-'Generic' Enums:--@-instance Enumerable ... where- 'enumerated' = enumEnumerated-@--the enum should still be bounded. - --}-enumEnumerated :: (Enum a) => [a]-enumEnumerated = enumFrom (toEnum 0)--{- | for non-'Generic' Bounded Indexed ('Ix') types: --@-instance Enumerable _ where- 'enumerated' = indexedEnumerated- 'cardinality' = 'indexedCardinality'-@---}-indexedEnumerated :: (Bounded a, Ix a) => [a]-indexedEnumerated = range (minBound,maxBound)--{- | for non-'Generic' Bounded Indexed ('Ix') types. --} -indexedCardinality :: forall proxy a. (Bounded a, Ix a) => proxy a -> Natural -indexedCardinality _ = int2natural (rangeSize (minBound,maxBound::a))--{-| enumerate only when the cardinality is small enough. -returns the cardinality when too large. -->>> enumerateBelow 2 :: Either Natural [Bool] -Left 2-->>> enumerateBelow 100 :: Either Natural [Bool] -Right [False,True]--useful when you've established that traversing a list below some length-and consuming its values is reasonable for your application. -e.g. after benchmarking, you think you can process a billion entries within a minute. ---}-enumerateBelow :: forall a. (Enumerable a) => Natural -> Either Natural [a] -enumerateBelow maxSize = if theSize < maxSize then Right enumerated else Left theSize - where - theSize = cardinality (Proxy :: Proxy a)--{-| enumerate only when completely evaluating the list doesn't timeout -(before the given number of microseconds). -->>> enumerateTimeout (2 * 10^6) :: IO (Maybe [Bool]) -- two seconds -Just [False,True]---}-enumerateTimeout :: (Enumerable a, NFData a) => Int -> IO (Maybe [a])-enumerateTimeout maxDuration = timeout maxDuration (return$ force enumerated)-+{-# LANGUAGE RankNTypes, ScopedTypeVariables, DefaultSignatures, TypeOperators, FlexibleInstances, FlexibleContexts, LambdaCase, DataKinds #-} +{- | see the 'Enumerable' class for documentation. + +see "Data.Enumerate.Example" for examples. + +can also help automatically derive @<https://hackage.haskell.org/package/QuickCheck/docs/Test-QuickCheck-Arbitrary.html QuickCheck>@ instances: + +@ +newtype SmallNatural = ... +instance Enumerable SmallNatural where ... +newtype SmallString = ... +instance Enumerable SmallString where ... +data T = C0 | C1 () Bool SmallNatural SmallString | C2 ... +instance Arbitrary T where arbitrary = elements 'enumerated' +@ + + +background on @Generics@: + +* <https://hackage.haskell.org/package/base-4.8.1.0/docs/GHC-Generics.html GHC.Generics> + + +also provides instances for: + +* sets + +* modular integers + +* vinyl records + + +related packages: + +* <http://hackage.haskell.org/package/emgm-0.4/docs/Generics-EMGM-Functions-Enum.html emgm>. allows infinite lists (by convention). too heavyweight. + +* <http://hackage.haskell.org/package/enumerable enumerable>. no @Generic@ instance. + +* <https://hackage.haskell.org/package/testing-feat-0.4.0.2/docs/Test-Feat-Class.html#t:Enumerable testing-feat>. too heavyweight (testing framework). + +* <https://hackage.haskell.org/package/smallcheck smallcheck> too heavyweight (testing framework). Series enumerates up to some depth and can enumerated infinitely-inhabited types. + +* https://hackage.haskell.org/package/quickcheck quickcheck> too heavyweight (testing framework, randomness unnecessary). + +-} + +module Data.Enumerate.Types where +import Data.Enumerate.Extra + +--import Data.Modular +import Data.Vinyl (Rec(..)) +import Control.Monad.Catch (MonadThrow(..)) + +import GHC.Generics +import Data.Proxy +import Control.Arrow ((&&&)) +import Data.List (genericLength) +import Data.Void (Void) +import Data.Word (Word8, Word16) +import Data.Int (Int8, Int16) +import qualified Data.Set as Set +import Data.Set (Set) +import System.Timeout +import Control.DeepSeq (NFData,force) +import GHC.TypeLits +import Numeric.Natural +import Data.Ix + + +{- | enumerate the set of all values in a (finitely enumerable) type. enumerates depth first. + +generalizes 'Enum's to any finite/discrete type. an Enumerable is either: + +* an Enum +* a product of Enumerables +* a sum of Enumerables + +can be implemented automatically via its 'Generic' instance. + +laws: + +* consistent: + + * @'cardinality' = 'length' 'enumerated'@ + + so you can index the 'enumerated' with a nonnegative index below the 'cardinality'. + +* distinct: + + * @(Eq a) => 'nub' 'enumerated' == 'enumerated'@ + +* complete: + + * @x `'elem'` 'enumerated'@ + +* coincides with @Bounded@ @Enum@s: + + * @('Enum' a, 'Bounded' a) => 'enumerated' == 'boundedEnumerated'@ + + * @('Enum' a) => 'enumerated' == 'enumEnumerated'@ + +(@Bounded@ constraint elided for convenience, but relevant.) + +("inputs" a type, outputs a list of values). + +-} +class Enumerable a where + + enumerated :: [a] + + default enumerated :: (Generic a, GEnumerable (Rep a)) => [a] + enumerated = to <$> genumerated + + cardinality :: proxy a -> Natural + cardinality _ = genericLength (enumerated :: [a]) + -- overrideable for performance, but don't lie! + + -- default cardinality :: (Generic a, GEnumerable (Rep a)) => proxy a -> Natural + -- cardinality _ = gcardinality (Proxy :: Proxy (Rep a)) + -- TODO merge both methods into one that returns their pair + +{-| a (safely-)partial function. i.e. a function that: + +* fails only via the 'throwM' method of 'MonadThrow' +* succeeds only via the 'return' method of 'Monad' + + +-} +type Partial a b = (forall m. MonadThrow m => a -> m b) + +-- | "Generic Enumerable", lifted to unary type constructors. +class GEnumerable f where + genumerated :: [f x] + gcardinality :: proxy f -> Natural + +-- | empty list +instance GEnumerable (V1) where + genumerated = [] + gcardinality _ = 0 + {-# INLINE gcardinality #-} + +-- | singleton list +instance GEnumerable (U1) where + genumerated = [U1] + gcardinality _ = 1 + {-# INLINE gcardinality #-} + +{-| call 'enumerated' + +-} +instance (Enumerable a) => GEnumerable (K1 R a) where + genumerated = K1 <$> enumerated + gcardinality _ = cardinality (Proxy :: Proxy a) + {-# INLINE gcardinality #-} + +-- | multiply lists with @concatMap@ +instance (GEnumerable (f), GEnumerable (g)) => GEnumerable (f :*: g) where + genumerated = (:*:) <$> genumerated <*> genumerated + gcardinality _ = gcardinality (Proxy :: Proxy (f)) * gcardinality (Proxy :: Proxy (g)) + {-# INLINE gcardinality #-} + +-- | add lists with @(<>)@ +instance (GEnumerable (f), GEnumerable (g)) => GEnumerable (f :+: g) where + genumerated = map L1 genumerated ++ map R1 genumerated + gcardinality _ = gcardinality (Proxy :: Proxy (f)) + gcardinality (Proxy :: Proxy (g)) + {-# INLINE gcardinality #-} + +-- | ignore selector metadata +instance (GEnumerable (f)) => GEnumerable (M1 S t f) where + genumerated = M1 <$> genumerated + gcardinality _ = gcardinality (Proxy :: Proxy (f)) + {-# INLINE gcardinality #-} + +-- | ignore constructor metadata +instance (GEnumerable (f)) => GEnumerable (M1 C t f) where + genumerated = M1 <$> genumerated + gcardinality _ = gcardinality (Proxy :: Proxy (f)) + {-# INLINE gcardinality #-} + +-- | ignore datatype metadata +instance (GEnumerable (f)) => GEnumerable (M1 D t f) where + genumerated = M1 <$> genumerated + gcardinality _ = gcardinality (Proxy :: Proxy (f)) + {-# INLINE gcardinality #-} + +{-| see "Data.Enumerate.Reify.getJectivityM" + +-} +data Jectivity = Injective | Surjective | Bijective deriving (Show,Read,Eq,Ord,Enum,Bounded) + +{-| wrap any @(Bounded a, Enum a)@ to be a @Enumerable@ via 'boundedEnumerated'. + +(avoids @OverlappingInstances@). + +-} +newtype WrappedBoundedEnum a = WrappedBoundedEnum { unwrapBoundedEnum :: a } + +instance (Bounded a, Enum a) => Enumerable (WrappedBoundedEnum a) where + enumerated = WrappedBoundedEnum <$> boundedEnumerated + cardinality _ = boundedCardinality (Proxy :: Proxy a) + +-- base types +instance Enumerable Void +instance Enumerable () +instance Enumerable Bool +instance Enumerable Ordering + +{- | + +>>> (maxBound::Int8) - (minBound::Int8) +256 + +-} +instance Enumerable Int8 where enumerated = boundedEnumerated; cardinality = boundedCardinality +instance Enumerable Word8 where enumerated = boundedEnumerated; cardinality = boundedCardinality +{- | + +>>> (maxBound::Int16) - (minBound::Int16) +65535 + +-} +instance Enumerable Int16 where enumerated = boundedEnumerated; cardinality = boundedCardinality +instance Enumerable Word16 where enumerated = boundedEnumerated; cardinality = boundedCardinality +{- | there are only a million (1,114,112) characters. + +>>> ord minBound +0 + +>>> ord maxBound +1114111 + +>>> length [chr 0..] +1114112 + +-} +instance Enumerable Char where enumerated = boundedEnumerated; cardinality = boundedCardinality + +{-| the sum type. + +the 'cardinality' is the sum of the cardinalities of @a@ and @b@. + +-} +instance (Enumerable a, Enumerable b) => Enumerable (Either a b) where + enumerated = (Left <$> enumerated) ++ (Right <$> enumerated) + cardinality _ = cardinality (Proxy :: Proxy a) + cardinality (Proxy :: Proxy b) +instance (Enumerable a) => Enumerable (Maybe a) where + enumerated = Nothing : (Just <$> enumerated) + cardinality _ = 1 + cardinality (Proxy :: Proxy a) + +{-| the product type. + +the 'cardinality' is the product of the cardinalities of @a@ and @b@. + +-} +instance (Enumerable a, Enumerable b) => Enumerable (a, b) where + enumerated = (,) <$> enumerated <*> enumerated + cardinality _ = cardinality (Proxy :: Proxy a) * cardinality (Proxy :: Proxy b) + +instance (Enumerable a, Enumerable b, Enumerable c) => Enumerable (a, b, c) +instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d) => Enumerable (a, b, c, d) +instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d, Enumerable e) => Enumerable (a, b, c, d, e) +instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d, Enumerable e, Enumerable f) => Enumerable (a, b, c, d, e, f) +instance (Enumerable a, Enumerable b, Enumerable c, Enumerable d, Enumerable e, Enumerable f, Enumerable g) => Enumerable (a, b, c, d, e, f, g) + +{-| the cardinality is product of cardinalities. -} +instance (Enumerable (f a), Enumerable (Rec f as)) => Enumerable (Rec f (a ': as)) where + enumerated = (:&) <$> enumerated <*> enumerated + cardinality _ = cardinality (Proxy :: Proxy (f a)) * cardinality (Proxy :: Proxy (Rec f as)) + +{-| the cardinality is 1. -} +instance Enumerable (Rec f '[]) where + enumerated = [RNil] + cardinality _ = 1 + +{-| + +the 'cardinality' is the cardinality of the 'powerSet' of @a@, i.e. @2^|a|@. +warning: it grows quickly. don't try to take the power set of 'Char'! or even 'Word8'. + +the 'cardinality' call is efficient (depending on the efficiency of the base type's call). +you should be able to safely call 'enumerateBelow', unless the arithmetic itself becomes too large. + + +-} +instance (Enumerable a, Ord a) => Enumerable (Set a) where + enumerated = (Set.toList . powerSet . Set.fromList) enumerated + cardinality _ = 2 ^ cardinality (Proxy :: Proxy a) + +{- +-- | (from the @modular-arithmetic@ package) +instance (Integral i, Num i, KnownNat n) => Enumerable (Mod i n) where + enumerated = toMod <$> [0 .. fromInteger (natVal (Proxy :: Proxy n) - 1)] + cardinality _ = fromInteger (natVal (Proxy :: Proxy n)) +-} + +{- | for non-'Generic' Bounded Enums: + +@ +instance Enumerable _ where + 'enumerated' = boundedEnumerated + 'cardinality' = 'boundedCardinality' +@ + +-} +boundedEnumerated :: (Bounded a, Enum a) => [a] +boundedEnumerated = enumFromTo minBound maxBound + +{-| for non-'Generic' Bounded Enums. + +behavior may be undefined when the cardinality of @a@ is larger than the cardinality of @Int@. this should be okay, as @Int@ is at least as big as @Int64@, which is at least as big as all the monomorphic types in @base@ that instantiate @Bounded@. you can double-check with: + +>>> boundedCardinality (const(undefined::Int)) -- platform specific +18446744073709551616 + +@-- i.e. 1 + 9223372036854775807 - -9223372036854775808@ + +works with non-zero-based Enum instances, like @Int64@ or a custom @toEnum/fromEnum@. +assumes the enumeration's numbering is contiguous, e.g. if @fromEnum 0@ and @fromEnum 2@ +both exist, then @fromEnum 1@ should exist too. + +-} +boundedCardinality :: forall proxy a. (Bounded a, Enum a) => proxy a -> Natural +boundedCardinality _ = fromInteger (1 + (toInteger (fromEnum (maxBound::a))) - (toInteger (fromEnum (minBound::a)))) + +{- | for non-'Generic' Enums: + +@ +instance Enumerable ... where + 'enumerated' = enumEnumerated +@ + +the enum should still be bounded. + +-} +enumEnumerated :: (Enum a) => [a] +enumEnumerated = enumFrom (toEnum 0) + +{- | for non-'Generic' Bounded Indexed ('Ix') types: + +@ +instance Enumerable _ where + 'enumerated' = indexedEnumerated + 'cardinality' = 'indexedCardinality' +@ + +-} +indexedEnumerated :: (Bounded a, Ix a) => [a] +indexedEnumerated = range (minBound,maxBound) + +{- | for non-'Generic' Bounded Indexed ('Ix') types. +-} +indexedCardinality :: forall proxy a. (Bounded a, Ix a) => proxy a -> Natural +indexedCardinality _ = int2natural (rangeSize (minBound,maxBound::a)) + +{-| enumerate only when the cardinality is small enough. +returns the cardinality when too large. + +>>> enumerateBelow 2 :: Either Natural [Bool] +Left 2 + +>>> enumerateBelow 100 :: Either Natural [Bool] +Right [False,True] + +useful when you've established that traversing a list below some length +and consuming its values is reasonable for your application. +e.g. after benchmarking, you think you can process a billion entries within a minute. + +-} +enumerateBelow :: forall a. (Enumerable a) => Natural -> Either Natural [a] +enumerateBelow maxSize = if theSize < maxSize then Right enumerated else Left theSize + where + theSize = cardinality (Proxy :: Proxy a) + +{-| enumerate only when completely evaluating the list doesn't timeout +(before the given number of microseconds). + +>>> enumerateTimeout (2 * 10^6) :: IO (Maybe [Bool]) -- two seconds +Just [False,True] + +-} +enumerateTimeout :: (Enumerable a, NFData a) => Int -> IO (Maybe [a]) +enumerateTimeout maxDuration = timeout maxDuration (return$ force enumerated)