generic-enum (empty) → 0.1.0.0
raw patch · 5 files changed
+642/−0 lines, 5 filesdep +arraydep +basedep +bytestringsetup-changed
Dependencies added: array, base, bytestring, hspec
Files
- LICENSE +19/−0
- Setup.hs +2/−0
- generic-enum.cabal +43/−0
- src/Data/Generic/Enum.hs +528/−0
- test/Tests.hs +50/−0
+ LICENSE view
@@ -0,0 +1,19 @@+Copyright 2017 Clinton Mead++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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ generic-enum.cabal view
@@ -0,0 +1,43 @@+name: generic-enum+version: 0.1.0.0+synopsis: An Enum class that fixes some deficiences with Prelude's Enum+description:+ The default Enum class has a couple of issues that limit it's flexibility namely:+ .+ 1) The \"Enum\" type must be always \"Int\", even when this loses information, such as in+ the case of \"Integer\" (due to overflow) and \"Rational\" (in the case of fractions).+ .+ 2) "enumFromTo" etc like functions can only produce lists, instead of directly producing other+ list like data types. Aside from effeciency, using a list as an intermediary can lose information also.+ .+ The Enum class this package defines should be largely a drop in replacement for Prelude's Enum,+ with some minor exceptions detailed in the documentation.+license: MIT+license-file: LICENSE+copyright: Clinton Mead (2017)+homepage: +author: Clinton Mead+maintainer: clintonmead@gmail.com+category: +build-type: Simple+cabal-version: >=1.10+tested-with: GHC == 8.0.2+bug-reports: https://github.com/clintonmead/generic-enum/issues++source-repository head+ type: git+ location: https://github.com/clintonmead/generic-enum.git++library+ exposed-modules: Data.Generic.Enum+ build-depends: base == 4.9.*, array == 0.5.*, bytestring == 0.10.*+ hs-source-dirs: src+ default-language: Haskell2010++Test-Suite tests+ type: exitcode-stdio-1.0+ other-modules: Data.Generic.Enum+ main-is: Tests.hs+ build-depends: base == 4.9.*, array == 0.5.*, bytestring == 0.10.*, hspec == 2.4.*+ hs-source-dirs: src, test+ default-language: Haskell2010
+ src/Data/Generic/Enum.hs view
@@ -0,0 +1,528 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE DefaultSignatures #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE FlexibleInstances #-}++module Data.Generic.Enum (+ Enum(succ, pred, toEnum, fromEnum, numStepsBetween),+ DefaultEnum(defaultFromEnum, defaultToEnum),+ Element,+ EnumFromTo(enumFromTo, enumFromThenTo, enumFromThenCount, enumFromCount, enumFromStepTo, enumFromStepCount),+ EnumFrom(enumFrom, enumFromThen, enumFromStep)+ ) where++import qualified Prelude+import Prelude hiding (Enum, fromEnum, toEnum, succ, pred, enumFrom, enumFromThen, enumFromTo, enumFromThenTo)++import Data.Int+import Data.Word+import Data.Char+import System.IO+import GHC.Generics+import Foreign.C.Types+import Foreign.Ptr+import System.Posix.Types+import Numeric.Natural+import GHC.RTS.Flags+import Data.Ratio+import Data.Fixed+import Data.Semigroup+import Data.Functor.Identity+import Data.Proxy+import Data.Type.Equality+import Data.Coerce+import Data.Type.Coercion+import Data.Monoid (Alt(Alt), getAlt)+import Data.Functor.Const+import qualified GHC.Enum++import Data.Array (Array, listArray, Ix, elems)+import Data.List (genericLength)++import qualified Data.ByteString as BS+import qualified Data.ByteString.Lazy as BSL+import qualified Data.ByteString.Short as BSS++{-|+The generic 'Enum' class. Firstly, this class just deals with 'fromEnum', 'toEnum' type functions,+not the list generating functions like 'Prelude.enumFrom' and 'Prelude.enumFromTo'+the normal 'Prelude.Enum' has.++This class has a number of defaults for making defining both existing Prelude style Enum classes and+ordinary Numeric classes quick and painless.++Firstly, for existing Enums:++> instance Enum Blah++Will completely define @Blah@ as an @Enum@ if @Blah@ is already a Prelude style Enum, just forwarding+calls to the functions in the Prelude's Enum.++Secondly, for integral datatypes (i.e. in class 'Integral')++> instance Enum Blah+> type EnumNumT Blah = Blah++will defined @Blah@ to be an Enum, with it's Enum type itself.++For example,++> instance Enum Integer+> type EnumNumT Integer = Integer++is an Enum with 'fromEnum' and 'toEnum' simply 'Prelude.id'.++Note that with this approach, @toEnum . fromEnum == id@, instead of going through 'Int'+and possibly overflowing.++Note also that operations like 'succ' and 'pred' don't bounds check like the Prelude versions often do.++For types that don't fit one of the above two categories (i.e. don't have a satisfactory Prelude Enum+instance or aren't Integral) you'll have to define the individual functions as discussed with their documentation.++Note that the following function, whilst valid with Prelude style enums, is not valid with the 'Enum' class in this module:++> convertEnum :: (Enum a, Enum b) => a -> b+> convertEnum = toEnum . fromEnum++because now, Enum's can have different "enum types". That is. 'fromEnum' is not always an 'Int', and 'toEnum' does+not always take an 'Int'.++Though it is debatable if the above function is sensible though anyway.++I have attempted to define instances of 'Enum' for all types in GHCs included libraries, tell me+if I've missed any though.+-}+class (Num (EnumNumT a), Integral (EnumIntegralT a)) => Enum a where+ {-|+ This is the \"enum\" type. It just needs to be in the class 'Num'.+ -}+ type EnumNumT a+ type EnumNumT a = Int++ {-|+ 'EnumIntegralT' (default - 'EnumNumT'): this is a type that represents the number of \"steps\"+ between two enums, based on a stepsize. Whilst 'EnumNumT' must only be a 'Num', 'EnumIntegralT'+ needs to be 'Integral'. If 'EnumNumT' is already 'Integral' it's almost certainly a good choice.+ -}+ type EnumIntegralT a+ type EnumIntegralT a = EnumNumT a++ succ :: a -> a+ succ = toEnum . (+ 1) . fromEnum++ pred :: a -> a+ pred = toEnum . (subtract 1) . fromEnum++ {-| Just like Prelude's 'Prelude.toEnum', but with @EnumNumT t@ instead of 'Int' -}+ toEnum :: EnumNumT a -> a+ default toEnum :: (DefaultEnum a (EnumNumT a)) => EnumNumT a -> a+ toEnum = defaultToEnum++ {-| Just like Prelude's 'Prelude.fromEnum', but with @EnumNumT t@ instead of 'Int' -}+ fromEnum :: a -> EnumNumT a+ default fromEnum :: (DefaultEnum a (EnumNumT a)) => a -> EnumNumT a+ fromEnum = defaultFromEnum++ {-|+ 'numStepsBetween': This takes three arguments, firstly, two of type @t@ for some @Enum t@ (\"@start\"@ and \"@end\"@,+ and also \"@step@\" of @EnumNumT t@, i.e. the \"enum\" type of @t@.++ The result should be the length of the following list:++ > [start, (start + step) .. end]++ and also of type @EnumIntegralT t@. It should not be less than 0.++ For example:++ > numStepsBetween 'a' 'e' 2++ should be 3.+ -}+ numStepsBetween :: a -> a -> EnumNumT a -> EnumIntegralT a+ default numStepsBetween :: (e ~ EnumNumT a, e ~ EnumIntegralT a) => a -> a -> e -> e+ numStepsBetween x y stepSize = max ((fromEnum y - fromEnum x) `div` stepSize + 1) 0++{-|+A little trick for defining the two default cases mentioned in the documentation for 'Enum'.+-}+class DefaultEnum a b where+ defaultFromEnum :: a -> b+ defaultToEnum :: b -> a++instance (Prelude.Enum a) => DefaultEnum a Int where+ defaultFromEnum = Prelude.fromEnum+ defaultToEnum = Prelude.toEnum++instance DefaultEnum a a where+ defaultFromEnum = id+ defaultToEnum = id++instance {-# OVERLAPPING #-} DefaultEnum Int Int where+ defaultFromEnum = id+ defaultToEnum = id++instance Enum Bool+instance Enum Char+instance Enum Int++instance Enum Int8 where+ type EnumNumT Int8 = Int8++instance Enum Int16 where+ type EnumNumT Int16 = Int16++instance Enum Int32 where+ type EnumNumT Int32 = Int32++instance Enum Int64 where+ type EnumNumT Int64 = Int64++instance Enum Integer where+ type EnumNumT Integer = Integer++instance Enum Ordering++instance Enum Word8 where+ type EnumNumT Word8 = Word8++instance Enum Word16 where+ type EnumNumT Word16 = Word16++instance Enum Word32 where+ type EnumNumT Word32 = Word32++instance Enum Word64 where+ type EnumNumT Word64 = Word64++instance Enum ()++instance Enum GeneralCategory+instance Enum IOMode+instance Enum DecidedStrictness+instance Enum SourceStrictness+instance Enum SourceUnpackedness+instance Enum Associativity+instance Enum SeekMode++instance Enum CUIntMax where+ type EnumNumT CUIntMax = CUIntMax++instance Enum CIntMax where+ type EnumNumT CIntMax = CIntMax++instance Enum CUIntPtr where+ type EnumNumT CUIntPtr = CUIntPtr++instance Enum CIntPtr where+ type EnumNumT CIntPtr = CIntPtr++instance Enum CSUSeconds+instance Enum CUSeconds+instance Enum CTime+instance Enum CClock++instance Enum CSigAtomic where+ type EnumNumT CSigAtomic = CSigAtomic++instance Enum CWchar where+ type EnumNumT CWchar = CWchar++instance Enum CSize where+ type EnumNumT CSize = CSize++instance Enum CPtrdiff where+ type EnumNumT CPtrdiff = CPtrdiff++instance Enum CDouble+instance Enum CFloat+instance Enum CULLong where+ type EnumNumT CULLong = CULLong++instance Enum CLLong where+ type EnumNumT CLLong = CLLong++instance Enum CULong where+ type EnumNumT CULong = CULong++instance Enum CLong where+ type EnumNumT CLong = CLong++instance Enum CUInt where+ type EnumNumT CUInt = CUInt++instance Enum CInt where+ type EnumNumT CInt = CInt++instance Enum CUShort where+ type EnumNumT CUShort = CUShort++instance Enum CShort where+ type EnumNumT CShort = CShort++instance Enum CUChar where+ type EnumNumT CUChar = CUChar++instance Enum CSChar where+ type EnumNumT CSChar = CSChar++instance Enum CChar where+ type EnumNumT CChar = CChar++instance Enum IntPtr where+ type EnumNumT IntPtr = IntPtr++instance Enum WordPtr where+ type EnumNumT WordPtr = WordPtr++instance Enum Fd where+ type EnumNumT Fd = Fd++instance Enum CRLim where+ type EnumNumT CRLim = CRLim++instance Enum CTcflag where+ type EnumNumT CTcflag = CTcflag++instance Enum CSpeed+instance Enum CCc++instance Enum CUid where+ type EnumNumT CUid = CUid++instance Enum CNlink where+ type EnumNumT CNlink = CNlink++instance Enum CGid where+ type EnumNumT CGid = CGid++instance Enum CSsize where+ type EnumNumT CSsize = CSsize++instance Enum CPid where+ type EnumNumT CPid = CPid++instance Enum COff where+ type EnumNumT COff = COff++instance Enum CMode where+ type EnumNumT CMode = CMode++instance Enum CIno where+ type EnumNumT CIno = CIno++instance Enum CDev where+ type EnumNumT CDev = CDev++instance Enum Natural where+ type EnumNumT Natural = Natural++instance Enum DoTrace+instance Enum DoHeapProfile+instance Enum DoCostCentres+instance Enum GiveGCStats++instance (Integral a) => Enum (Ratio a) where+ type EnumNumT (Ratio a) = Ratio a+ type EnumIntegralT (Ratio a) = a+ numStepsBetween x y stepSize = max (floor ((y - x) / stepSize + 1)) 0++instance (HasResolution a) => Enum (Fixed a) where+ type EnumNumT (Fixed a) = Fixed a+ type EnumIntegralT (Fixed a) = Integer+ numStepsBetween x y stepSize = max (floor ((y - x) / stepSize + 1)) 0++instance Enum a => Enum (WrappedMonoid a) where+ type EnumNumT (WrappedMonoid a) = EnumNumT a+ type EnumIntegralT (WrappedMonoid a) = EnumIntegralT a+ succ (WrapMonoid a) = WrapMonoid (succ a)+ pred (WrapMonoid a) = WrapMonoid (pred a)+ toEnum = WrapMonoid . toEnum+ fromEnum = fromEnum . unwrapMonoid+ numStepsBetween (WrapMonoid x) (WrapMonoid y) stepSize = numStepsBetween x y stepSize++instance Enum a => Enum (Last a) where+ type EnumNumT (Last a) = EnumNumT a+ type EnumIntegralT (Last a) = EnumIntegralT a+ succ (Last a) = Last (succ a)+ pred (Last a) = Last (pred a)+ toEnum = Last . toEnum+ fromEnum = fromEnum . getLast+ numStepsBetween (Last x) (Last y) stepSize = numStepsBetween x y stepSize++instance Enum a => Enum (First a) where+ type EnumNumT (First a) = EnumNumT a+ type EnumIntegralT (First a) = EnumIntegralT a+ succ (First a) = First (succ a)+ pred (First a) = First (pred a)+ toEnum = First . toEnum+ fromEnum = fromEnum . getFirst+ numStepsBetween (First x) (First y) stepSize = numStepsBetween x y stepSize++instance Enum a => Enum (Max a) where+ type EnumNumT (Max a) = EnumNumT a+ type EnumIntegralT (Max a) = EnumIntegralT a+ succ (Max a) = Max (succ a)+ pred (Max a) = Max (pred a)+ toEnum = Max . toEnum+ fromEnum = fromEnum . getMax+ numStepsBetween (Max x) (Max y) stepSize = numStepsBetween x y stepSize++instance Enum a => Enum (Min a) where+ type EnumNumT (Min a) = EnumNumT a+ type EnumIntegralT (Min a) = EnumIntegralT a+ succ (Min a) = Min (succ a)+ pred (Min a) = Min (pred a)+ toEnum = Min . toEnum+ fromEnum = fromEnum . getMin+ numStepsBetween (Min x) (Min y) stepSize = numStepsBetween x y stepSize++instance Enum a => Enum (Identity a) where+ type EnumNumT (Identity a) = EnumNumT a+ type EnumIntegralT (Identity a) = EnumIntegralT a+ succ (Identity a) = Identity (succ a)+ pred (Identity a) = Identity (pred a)+ toEnum = Identity . toEnum+ fromEnum = fromEnum . runIdentity+ numStepsBetween (Identity x) (Identity y) stepSize = numStepsBetween x y stepSize++instance Enum (Proxy s)+instance ((~) a b) => Enum ((:~:) a b)+instance (Coercible a b) => Enum (Coercion a b)++instance Enum (f a) => Enum (Alt f a) where+ type EnumNumT (Alt f a) = EnumNumT (f a)+ type EnumIntegralT (Alt f a) = EnumIntegralT (f a)+ succ (Alt x) = Alt (succ x)+ pred (Alt x) = Alt (pred x)+ toEnum = Alt . toEnum+ fromEnum = fromEnum . getAlt+ numStepsBetween (Alt x) (Alt y) stepSize = numStepsBetween x y stepSize++instance Enum a => Enum (Const a b) where+ type EnumNumT (Const a b) = EnumNumT a+ type EnumIntegralT (Const a b) = EnumIntegralT a+ succ (Const x) = Const (succ x)+ pred (Const x) = Const (pred x)+ toEnum = Const . toEnum+ fromEnum = fromEnum . getConst+ numStepsBetween (Const x) (Const y) stepSize = numStepsBetween x y stepSize++{-|+This specifies the type of elements of an instance of a class of either 'EnumFromTo' or 'EnumFrom'.++For example, the definition for lists is:++> type instance Element [a] = a+-}+type family Element a++{-|+The 'EnumFromTo' class defines versions of the Prelude 'Prelude.Enum' functions+'Prelude.enumFromTo' and 'Prelude.enumFromThenTo', as well as other functions which+may sometimes be more convienient.++But more importantly, it can produce any structure you define an instance for,+not just lists.++The only function that needs to be defined is 'enumFromStepCount',+default definitions will look after the rest.++Note that this class does not deal with the infinite list generating functions,+you'll need to look at the 'EnumFrom' class for that.++I've attempted to define appropriate instances for any structures in the core GHC+distribution, currently lists, arrays and bytestrings.+-}+class Enum (Element a) => EnumFromTo a where+ {-| Much like 'Prelude.enumFromTo' from Prelude -}+ enumFromTo :: Element a -> Element a -> a+ enumFromTo x = enumFromStepTo x 1++ {-| Much like 'Prelude.enumFromThenTo' from Prelude -}+ enumFromThenTo :: Element a -> Element a -> Element a -> a+ enumFromThenTo x next_x y = enumFromStepTo x (fromEnum next_x - fromEnum x) y++ {-| This is like 'enumFromTo', but instead of a final stopping number, a count is given. -}+ enumFromCount :: Element a -> EnumIntegralT (Element a) -> a+ enumFromCount x = enumFromStepCount x 1++ {-| This is like 'enumFromThenTo', but instead of a final stopping number, a count is given. -}+ enumFromThenCount :: Element a -> Element a -> EnumIntegralT (Element a) -> a+ enumFromThenCount x next_x = enumFromStepCount x (fromEnum next_x - fromEnum x)++ {-| This is like 'enumFromThenTo', but instead of giving the second element directly, a step size is passed. -}+ enumFromStepTo :: Element a -> EnumNumT (Element a) -> Element a -> a+ enumFromStepTo x stepSize y = enumFromStepCount x stepSize (numStepsBetween x y stepSize)++ {-|+ This is a combination of the conviencience changes in 'enumFromThenCount' and 'enumFromStepTo'.++ Instead of having to explicitly state the second element, a \"stepsize\" is passed,+ Also, instead of stating the last element, a \"count\" is passed.++ I find this tends to be more useful more often.+ -}+ enumFromStepCount :: Element a -> EnumNumT (Element a) -> EnumIntegralT (Element a) -> a++{-|+Much like the 'EnumFromTO' class, but defines the \"infinite\" Prelude Enum functions, namely+'Prelude.enumFrom' and 'Prelude.enumFromThen', as well as 'enumFromStep'.++The only function that needs to be defined is 'enumFromStep',+default definitions will look after the rest.+-}+class Enum (Element a) => EnumFrom a where+ {-| Much like 'Prelude.enumFrom' from Prelude -}+ enumFrom :: Element a -> a+ enumFrom x = enumFromStep x 1++ {-| Much like 'Prelude.enumFromThen' from Prelude -}+ enumFromThen :: Element a -> Element a -> a+ enumFromThen x next_x = enumFromStep x (fromEnum next_x - fromEnum x)++ {-| Like 'enumFromThen', but with an explicit step size, not just the second element given. -}+ enumFromStep :: Element a -> EnumNumT (Element a) -> a+ default enumFromStep :: (Bounded (EnumIntegralT (Element a)), EnumFromTo a) => Element a -> EnumNumT (Element a) -> a+ enumFromStep x stepSize = enumFromStepCount x stepSize maxBound++type instance Element [a] = a++instance Enum a => EnumFromTo [a] where+ enumFromStepCount x stepSize count = go (fromEnum x) count where+ go x n = case n of+ 0 -> []+ _ -> (toEnum x):(go (x + stepSize) (n-1))++instance Enum a => EnumFrom [a] where+ enumFromStep x stepSize = go (fromEnum x) where+ go x = (toEnum x):(go (x + stepSize))++type instance Element (Array i e) = e++instance (Enum e, Ix i, Num i) => EnumFromTo (Array i e) where+ enumFromStepCount x stepSize count = listArray (fromIntegral 0, fromIntegral (count - 1)) (enumFromStepCount x stepSize count)++type instance Element BS.ByteString = Word8+type instance Element BSL.ByteString = Word8+type instance Element BSS.ShortByteString = Word8++byteStringUnfoldrN f x stepSize count = let (r, _) = f (fromIntegral count) (\i -> Just (toEnum i, i + stepSize)) (fromEnum x) in r++instance EnumFromTo BS.ByteString where+ enumFromStepCount = byteStringUnfoldrN BS.unfoldrN++instance EnumFromTo BSL.ByteString where+ enumFromStepCount x stepSize count = BSL.unfoldr f (fromEnum x, count) where+ f (i, n) = case n of+ 0 -> Nothing+ _ -> Just (toEnum i, (i + stepSize, n - 1))++instance EnumFromTo BSS.ShortByteString where+ enumFromStepCount x stepSize count = BSS.toShort (byteStringUnfoldrN BS.unfoldrN x stepSize count)+
+ test/Tests.hs view
@@ -0,0 +1,50 @@+{-# LANGUAGE NoImplicitPrelude #-}+{-# LANGUAGE OverloadedLists #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE TypeApplications #-}+module Main where++import Prelude hiding (+ Enum, fromEnum, toEnum, succ, pred, enumFrom, enumFromThen, enumFromTo, enumFromThenTo+ )+import qualified Prelude as P+import Data.Generic.Enum++import Data.Generic.Enum (+ Element+ )++import Data.Proxy (Proxy(Proxy))+import Data.Ratio (Rational, (%))+import Data.Array (Array)+import Data.Word (Word8)++import qualified Data.ByteString as B++import Test.Hspec (hspec, it, shouldBe)++n1_int :: Int+n1_int = 2++n2_int :: Int+n2_int = 5++n1_rational :: Rational+n1_rational = 7 % 3++n2_rational :: Rational+n2_rational = 6 % 1++_a :: Word8+_a = P.toEnum (P.fromEnum 'a')++_e :: Word8+_e = P.toEnum (P.fromEnum 'e')++main = hspec $ do+ it "enumFromTo integer test" $ enumFromTo n1_int n2_int `shouldBe` P.enumFromTo n1_int n2_int+ it "enumFromTo rational test" $ sum ((enumFromTo n1_rational n2_rational) :: [Rational]) `shouldBe` (46 % 3)+ it "enumFromTo bytestring test" $ enumFromTo _a _e `shouldBe` B.pack [_a.._e]