Skip to content

Move toEnum&fromEnum into the Enum class, provide range functions. #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 1, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Gruntfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function(grunt) {

pscMake: ["<%=libFiles%>"],
dotPsci: ["<%=libFiles%>"],
docgen: {
pscDocs: {
readme: {
src: "src/**/*.purs",
dest: "README.md"
Expand All @@ -37,6 +37,6 @@ module.exports = function(grunt) {
grunt.loadNpmTasks("grunt-contrib-clean");
grunt.loadNpmTasks("grunt-purescript");

grunt.registerTask("make", ["pscMake", "dotPsci", "docgen", "jsvalidate"]);
grunt.registerTask("make", ["pscMake", "dotPsci", "pscDocs", "jsvalidate"]);
grunt.registerTask("default", ["make"]);
};
20 changes: 17 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
lastEnum :: a
succ :: a -> Maybe a
pred :: a -> Maybe a
toEnum :: Number -> Maybe a
fromEnum :: a -> Number


### Type Class Instances
Expand All @@ -33,8 +35,20 @@

### Values

fromEnum :: forall a. (Enum a) => a -> Number
defaultFromEnum :: forall a. (a -> Maybe a) -> a -> Number

runCardinality :: forall a. Cardinality a -> Number
defaultPred :: forall a. (Number -> Maybe a) -> (a -> Number) -> a -> Maybe a

toEnum :: forall a. (Enum a) => Number -> Maybe a
defaultSucc :: forall a. (Number -> Maybe a) -> (a -> Number) -> a -> Maybe a

defaultToEnum :: forall a. (a -> Maybe a) -> a -> Number -> Maybe a

enumFromThenTo :: forall a. (Enum a) => a -> a -> a -> [a]

enumFromTo :: forall a. (Enum a) => a -> a -> [a]

intFromTo :: Number -> Number -> [Number]

intStepFromTo :: Number -> Number -> Number -> [Number]

runCardinality :: forall a. Cardinality a -> Number
7 changes: 4 additions & 3 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
],
"dependencies": {
"purescript-maybe": "~0.2.1",
"purescript-tuples": "~0.2.1",
"purescript-either": "~0.1.3",
"purescript-strings": "~0.4.0"
"purescript-tuples": "~0.2.3",
"purescript-either": "~0.1.4",
"purescript-strings": "~0.4.2",
"purescript-unfoldable": "~0.2.0"
}
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"private": true,
"dependencies": {
"grunt": "~0.4.4",
"grunt-purescript": "~0.5.1",
"grunt-contrib-clean": "~0.5.0",
"grunt-jsvalidate": "~0.2.2"
"grunt-purescript": "~0.6.0",
"grunt-contrib-clean": "~0.6.0",
"grunt-jsvalidate": "~0.2.2"
}
}
182 changes: 159 additions & 23 deletions src/Data/Enum.purs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,22 @@ module Data.Enum
, runCardinality
, succ
, toEnum
, defaultSucc
, defaultPred
, defaultToEnum
, defaultFromEnum
, intFromTo
, intStepFromTo
, enumFromTo
, enumFromThenTo
) where

import Data.Maybe
import Data.Either
import Data.Tuple
import Data.Char
import Data.Maybe.Unsafe
import Data.Unfoldable

newtype Cardinality a = Cardinality Number

Expand All @@ -28,13 +37,23 @@ module Data.Enum
-- | to easily compute successor and predecessor elements. e.g. DayOfWeek, etc.
-- |
-- | Laws:
-- | succ firstEnum >>= succ >>= succ ... succ [cardinality times] == lastEnum
-- | pred lastEnum >>= pred >>= pred ... pred [cardinality times] == firstEnum
-- |
-- | Just $ e1 `compare` e2 == fromEnum e1 `compare` fromEnum e2
-- |
-- | succ firstEnum >>= succ >>= succ ... succ [cardinality - 1 times] == lastEnum
-- | pred lastEnum >>= pred >>= pred ... pred [cardinality - 1 times] == firstEnum
-- |
-- | e1 `compare` e2 == fromEnum e1 `compare` fromEnum e2
-- |
-- | for all a > firstEnum: pred a >>= succ == Just a
-- | for all a < lastEnum: succ a >>= pred == Just a
-- |
-- | pred >=> succ >=> pred = pred
-- | succ >=> pred >=> succ = succ
-- |
-- | toEnum (fromEnum a) = Just a
-- |
-- | for all a > firstEnum: fromEnum <$> pred a = Just (fromEnum a - 1)
-- | for all a < lastEnum: fromEnum <$> succ a = Just (fromEnum a + 1)


class (Ord a) <= Enum a where
cardinality :: Cardinality a

Expand All @@ -46,16 +65,62 @@ module Data.Enum

pred :: a -> Maybe a

toEnum :: forall a. (Enum a) => Number -> Maybe a
toEnum n | n < 0 = Nothing
toEnum 0 = Just firstEnum
toEnum n = toEnum (n - 1) >>= succ

fromEnum :: forall a. (Enum a) => a -> Number
fromEnum e = maybe 0 ((+) 1 <<< fromEnum) (pred e)

maybeCardinality :: forall a. (Enum a) => Cardinality a -> Cardinality (Maybe a)
maybeCardinality c = Cardinality $ 1 + (runCardinality c)
toEnum :: Number -> Maybe a

fromEnum :: a -> Number

-- | defaultSucc toEnum fromEnum = succ
defaultSucc :: forall a. (Number -> Maybe a) -> (a -> Number) -> (a -> Maybe a)
defaultSucc toEnum' fromEnum' a = toEnum' (fromEnum' a + 1)

-- | defaultPred toEnum fromEnum = pred
defaultPred :: forall a. (Number -> Maybe a) -> (a -> Number) -> (a -> Maybe a)
defaultPred toEnum' fromEnum' a = toEnum' (fromEnum' a - 1)

-- | Runs in O(n) where n is (fromEnum a)
-- | defaultToEnum succ firstEnum = toEnum
defaultToEnum :: forall a. (a -> Maybe a) -> a -> (Number -> Maybe a)
defaultToEnum succ' firstEnum' n | n < 0 = Nothing
defaultToEnum succ' firstEnum' 0 = Just firstEnum'
defaultToEnum succ' firstEnum' n = defaultToEnum succ' firstEnum' (n - 1) >>= succ'

-- | Runs in O(n) where n is (fromEnum a)
-- | defaultFromEnum pred = fromEnum
defaultFromEnum :: forall a. (a -> Maybe a) -> (a -> Number)
defaultFromEnum pred' e = maybe 0 (\prd -> defaultFromEnum pred' prd + 1) (pred' e)

-- Property: fromEnum a = a', fromEnum b = b' => forall e', a' <= e' <= b': Exists e: toEnum e' = Just e
-- Following from the propery of intFromTo, We are sure all elements in intFromTo (fromEnum a) (fromEnum b) are Justs.
enumFromTo :: forall a. (Enum a) => a -> a -> [a]
enumFromTo a b = (toEnum >>> fromJust) <$> intFromTo a' b'
where a' = fromEnum a
b' = fromEnum b

-- [a,b..c]
-- Correctness for using fromJust is the same as for enumFromTo.
enumFromThenTo :: forall a. (Enum a) => a -> a -> a -> [a]
enumFromThenTo a b c = (toEnum >>> fromJust) <$> intStepFromTo (b' - a') a' c'
where a' = fromEnum a
b' = fromEnum b
c' = fromEnum c

-- Property: forall e in intFromTo a b: a <= e <= b
-- intFromTo :: Int -> Int -> List Int
intFromTo :: Number -> Number -> [Number]
intFromTo = intStepFromTo 1

-- Property: forall e in intStepFromTo step a b: a <= e <= b
-- intStepFromTo :: Int -> Int -> Int -> List Int
intStepFromTo :: Number -> Number -> Number -> [Number]
intStepFromTo step from to =
unfoldr (\e ->
if e <= to
then Just $ Tuple e (e + step) -- Output the value e, set the next state to (e + step)
else Nothing -- End of the collection.
) from -- starting value/state.


-- | Instances

instance enumChar :: Enum Char where
cardinality = Cardinality (65535 + 1)
Expand All @@ -64,9 +129,21 @@ module Data.Enum

lastEnum = fromCharCode 65535

succ c = if c == lastEnum then Nothing else Just $ (fromCharCode <<< ((+) 1) <<< toCharCode) c
succ = defaultSucc charToEnum charFromEnum

pred c = if c == firstEnum then Nothing else Just $ (fromCharCode <<< ((+) (-1)) <<< toCharCode) c
pred = defaultPred charToEnum charFromEnum

toEnum = charToEnum

fromEnum = charFromEnum

-- To avoid a compiler bug - can't pass self-class functions, workaround: need to make a concrete function.
charToEnum :: Number -> Maybe Char
charToEnum n | n >= 0 && n <= 65535 = Just $ fromCharCode n
charToEnum _ = Nothing

charFromEnum :: Char -> Number
charFromEnum = toCharCode

instance enumMaybe :: (Enum a) => Enum (Maybe a) where
cardinality = maybeCardinality cardinality
Expand All @@ -81,18 +158,50 @@ module Data.Enum
pred Nothing = Nothing
pred (Just a) = Just <$> pred a

toEnum = maybeToEnum cardinality

fromEnum Nothing = 0
fromEnum (Just e) = fromEnum e + 1

maybeToEnum :: forall a. (Enum a) => Cardinality a -> Number -> Maybe (Maybe a)
maybeToEnum carda n | n <= runCardinality (maybeCardinality carda) =
if n == 0
then Just $ Nothing
else Just $ toEnum (n - 1)
maybeToEnum _ _ = Nothing

maybeCardinality :: forall a. (Enum a) => Cardinality a -> Cardinality (Maybe a)
maybeCardinality c = Cardinality $ 1 + (runCardinality c)

instance enumBoolean :: Enum Boolean where
cardinality = Cardinality 2

firstEnum = false
firstEnum = booleanFirstEnum

lastEnum = true

succ false = Just true
succ _ = Nothing
succ = booleanSucc

pred = booleanPred

toEnum = defaultToEnum booleanSucc booleanFirstEnum

fromEnum = defaultFromEnum booleanPred

booleanFirstEnum :: Boolean
booleanFirstEnum = false

booleanSucc :: Boolean -> Maybe Boolean
booleanSucc false = Just true
booleanSucc _ = Nothing

booleanPred :: Boolean -> Maybe Boolean
booleanPred true = Just false
booleanPred _ = Nothing

pred true = Just false
pred _ = Nothing
-- Until we get Int, floor and div in the prelude
foreign import floor "function floor(n){ return Math.floor(n); }" :: Number -> Number
div a b = floor (a / b)

instance enumTuple :: (Enum a, Enum b) => Enum (Tuple a b) where
cardinality = tupleCardinality cardinality cardinality
Expand All @@ -105,6 +214,17 @@ module Data.Enum

pred (Tuple a b) = maybe (flip Tuple firstEnum <$> pred a) (Just <<< Tuple a) (pred b)

toEnum = tupleToEnum cardinality

fromEnum = tupleFromEnum cardinality

-- All of these are as a workaround for ScopedTypeVariables. (not yet supported in Purescript)
tupleToEnum :: forall a b. (Enum a, Enum b) => Cardinality b -> Number -> Maybe (Tuple a b)
tupleToEnum cardb n = Tuple <$> (toEnum (n `div` (runCardinality cardb))) <*> (toEnum (n % (runCardinality cardb)))

tupleFromEnum :: forall a b. (Enum a, Enum b) => Cardinality b -> Tuple a b -> Number
tupleFromEnum cardb (Tuple a b) = (fromEnum a) * runCardinality cardb + fromEnum b

tupleCardinality :: forall a b. (Enum a, Enum b) => Cardinality a -> Cardinality b -> Cardinality (Tuple a b)
tupleCardinality l r = Cardinality $ (runCardinality l) * (runCardinality r)

Expand All @@ -121,5 +241,21 @@ module Data.Enum
pred (Left a) = maybe (Nothing) (Just <<< Left) (pred a)
pred (Right b) = maybe (Just $ Left lastEnum) (Just <<< Right) (pred b)

toEnum = eitherToEnum cardinality cardinality

fromEnum = eitherFromEnum cardinality

eitherToEnum :: forall a b. (Enum a, Enum b) => Cardinality a -> Cardinality b -> Number -> Maybe (Either a b)
eitherToEnum carda cardb n =
if n >= 0 && n < runCardinality carda
then Left <$> toEnum n
else if n >= (runCardinality carda) && n < runCardinality (eitherCardinality carda cardb)
then Right <$> toEnum (n - runCardinality carda)
else Nothing

eitherFromEnum :: forall a b. (Enum a, Enum b) => Cardinality a -> (Either a b -> Number)
eitherFromEnum carda (Left a) = fromEnum a
eitherFromEnum carda (Right b) = fromEnum b + runCardinality carda

eitherCardinality :: forall a b. (Enum a, Enum b) => Cardinality a -> Cardinality b -> Cardinality (Either a b)
eitherCardinality l r = Cardinality $ (runCardinality l) + (runCardinality r)
eitherCardinality l r = Cardinality $ (runCardinality l) + (runCardinality r)