Skip to content

Commit

Permalink
Separate Enum/Finite classes, fix bugs in the Maybe instance, and add…
Browse files Browse the repository at this point in the history
… Unit instance

* First pass mostly blindly converting things, type checks

* add Unit

* update docs

* fix maybeEnum pred

* duh... I just wrote id

* fix maybeEnum succ

* these laws also need to move to Finite

* update docs

* Finite -> BoundedEnum

* move to/fromEnum to BoundedEnum and make it build, added a couple of TODOs for funny things

* add defaultCardinality

* add Ord constraing on Enum

* add upFrom, downFrom

* use the Ord instance to implement enumFromTo (and make it more general while we're at it), we probably shouldn't be exposing the int functions from Data.Enum?

* enumEither does need both to be BoundedEnum, remove the comment questioning it

* we do need BoundedEnum for the to/fromEnum stuff on Tuple, but we can relax the Enum definition a bit

* w/ to/fromEnum in BoundeEnum we do indeed need it here

* fix copy/paste error in enumTuple

* w/ to/fromEnum in BoundeEnum we do indeed need it here
  • Loading branch information
jbrownson authored and garyb committed Apr 2, 2016
1 parent 94188c7 commit 51f7c06
Show file tree
Hide file tree
Showing 2 changed files with 141 additions and 73 deletions.
51 changes: 38 additions & 13 deletions docs/Data/Enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ runCardinality :: forall a. Cardinality a -> Int
#### `Enum`

``` purescript
class (Bounded a) <= Enum a where
cardinality :: Cardinality a
class Enum a where
succ :: a -> Maybe a
pred :: a -> Maybe a
toEnum :: Int -> Maybe a
Expand All @@ -26,29 +25,24 @@ class (Bounded a) <= Enum a where

Type class for enumerations. This should not be considered a part of a
numeric hierarchy, ala Haskell. Rather, this is a type class for small,
ordered sum types with statically-determined cardinality and the ability
to easily compute successor and predecessor elements. e.g. `DayOfWeek`, etc.
ordered sum types with the ability to easily compute successor and
predecessor elements. e.g. `DayOfWeek`, etc.

Laws:

- ```succ bottom >>= succ >>= succ ... succ [cardinality - 1 times] == top```
- ```pred top >>= pred >>= pred ... pred [cardinality - 1 times] == bottom```
- ```e1 `compare` e2 == fromEnum e1 `compare` fromEnum e2```
- ```forall a > bottom: pred a >>= succ == Just a```
- ```forall a < top: succ a >>= pred == Just a```
- ```pred >=> succ >=> pred = pred```
- ```succ >=> pred >=> succ = succ```
- ```toEnum (fromEnum a) = Just a```
- ```forall a > bottom: fromEnum <$> pred a = Just (fromEnum a - 1)```
- ```forall a < top: fromEnum <$> succ a = Just (fromEnum a + 1)```

##### Instances
``` purescript
instance enumUnit :: Enum Unit
instance enumChar :: Enum Char
instance enumMaybe :: (Enum a) => Enum (Maybe a)
instance enumMaybe :: (Finite a) => Enum (Maybe a)
instance enumBoolean :: Enum Boolean
instance enumTuple :: (Enum a, Enum b) => Enum (Tuple a b)
instance enumEither :: (Enum a, Enum b) => Enum (Either a b)
instance enumTuple :: (Finite a, Finite b) => Enum (Tuple a b)
instance enumEither :: (Finite a, Finite b) => Enum (Either a b)
```

#### `defaultSucc`
Expand Down Expand Up @@ -123,4 +117,35 @@ intStepFromTo :: Int -> Int -> Int -> Array Int

Property: ```forall e in intStepFromTo step a b: a <= e <= b```

#### `Finite`

``` purescript
class (Bounded a, Enum a) <= Finite a where
cardinality :: Cardinality a
```

Type class for finite enumerations. This should not be considered a part of
a numeric hierarchy, ala Haskell. Rather, this is a type class for small,
ordered sum types with statically-determined cardinality and the ability
to easily compute successor and predecessor elements. e.g. `DayOfWeek`, etc.

Laws:

- ```succ bottom >>= succ >>= succ ... succ [cardinality - 1 times] == top```
- ```pred top >>= pred >>= pred ... pred [cardinality - 1 times] == bottom```
- ```forall a > bottom: pred a >>= succ == Just a```
- ```forall a < top: succ a >>= pred == Just a```
- ```forall a > bottom: fromEnum <$> pred a = Just (fromEnum a - 1)```
- ```forall a < top: fromEnum <$> succ a = Just (fromEnum a + 1)```

##### Instances
``` purescript
instance finiteUnit :: Finite Unit
instance finiteChar :: Finite Char
instance finiteMaybe :: (Finite a) => Finite (Maybe a)
instance finiteBoolean :: Finite Boolean
instance finiteTuple :: (Finite a, Finite b) => Finite (Tuple a b)
instance finiteEither :: (Finite a, Finite b) => Finite (Either a b)
```


163 changes: 103 additions & 60 deletions src/Data/Enum.purs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Data.Enum
( Enum
, BoundedEnum
, Cardinality(..)
, cardinality
, fromEnum
Expand All @@ -9,12 +10,13 @@ module Data.Enum
, toEnum
, defaultSucc
, defaultPred
, defaultCardinality
, defaultToEnum
, defaultFromEnum
, intFromTo
, intStepFromTo
, enumFromTo
, enumFromThenTo
, upFrom
, downFrom
) where

import Prelude
Expand All @@ -32,29 +34,17 @@ runCardinality (Cardinality a) = a

-- | Type class for enumerations. This should not be considered a part of a
-- | numeric hierarchy, ala Haskell. Rather, this is a type class for small,
-- | ordered sum types with statically-determined cardinality and the ability
-- | to easily compute successor and predecessor elements. e.g. `DayOfWeek`, etc.
-- | ordered sum types with the ability to easily compute successor and
-- | predecessor elements. e.g. `DayOfWeek`, etc.
-- |
-- | Laws:
-- |
-- | - ```succ bottom >>= succ >>= succ ... succ [cardinality - 1 times] == top```
-- | - ```pred top >>= pred >>= pred ... pred [cardinality - 1 times] == bottom```
-- | - ```e1 `compare` e2 == fromEnum e1 `compare` fromEnum e2```
-- | - ```forall a > bottom: pred a >>= succ == Just a```
-- | - ```forall a < top: succ a >>= pred == Just a```
-- | - ```pred >=> succ >=> pred = pred```
-- | - ```succ >=> pred >=> succ = succ```
-- | - ```toEnum (fromEnum a) = Just a```
-- | - ```forall a > bottom: fromEnum <$> pred a = Just (fromEnum a - 1)```
-- | - ```forall a < top: fromEnum <$> succ a = Just (fromEnum a + 1)```


class (Bounded a) <= Enum a where
cardinality :: Cardinality a
class (Ord a) <= Enum a where
succ :: a -> Maybe a
pred :: a -> Maybe a
toEnum :: Int -> Maybe a
fromEnum :: a -> Int

-- | ```defaultSucc toEnum fromEnum = succ```
defaultSucc :: forall a. (Int -> Maybe a) -> (a -> Int) -> (a -> Maybe a)
Expand All @@ -64,41 +54,22 @@ defaultSucc toEnum' fromEnum' a = toEnum' (fromEnum' a + one)
defaultPred :: forall a. (Int -> Maybe a) -> (a -> Int) -> (a -> Maybe a)
defaultPred toEnum' fromEnum' a = toEnum' (fromEnum' a - one)

-- | Runs in `O(n)` where `n` is `fromEnum a`
-- |
-- | ```defaultToEnum succ bottom = toEnum```
defaultToEnum :: forall a. (a -> Maybe a) -> a -> (Int -> Maybe a)
defaultToEnum succ' bottom' n | n < zero = Nothing
| n == zero = Just bottom'
| otherwise = defaultToEnum succ' bottom' (n - one) >>= succ'

-- | Runs in `O(n)` where `n` is `fromEnum a`
-- |
-- | ```defaultFromEnum pred = fromEnum```
defaultFromEnum :: forall a. (a -> Maybe a) -> (a -> Int)
defaultFromEnum pred' e = maybe zero (\prd -> defaultFromEnum pred' prd + one) (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 `Just`s.
enumFromTo :: forall a. (Enum a) => a -> a -> Array a
enumFromTo a b = (toEnum >>> fromJust) <$> intFromTo a' b'
where a' = fromEnum a
b' = fromEnum b
-- TODO need to update the doc comment above
enumFromTo :: forall a u. (Enum a, Unfoldable u) => a -> a -> u a
enumFromTo from to = unfoldr (\x -> succ x >>= \x' -> if x <= to then pure $ Tuple x x' else Nothing) from

-- | `[a,b..c]`
-- |
-- | Correctness for using `fromJust` is the same as for `enumFromTo`.
enumFromThenTo :: forall a. (Enum a) => a -> a -> a -> Array a
enumFromThenTo :: forall a. (BoundedEnum a) => a -> a -> a -> Array 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 -> Array Int
intFromTo = intStepFromTo one

-- | Property: ```forall e in intStepFromTo step a b: a <= e <= b```
intStepFromTo :: Int -> Int -> Int -> Array Int
intStepFromTo step from to =
Expand All @@ -108,14 +79,72 @@ intStepFromTo step from to =
else Nothing -- End of the collection.
) from

diag a = Tuple a a

upFrom :: forall a u. (Enum a, Unfoldable u) => a -> u a
upFrom = unfoldr (map diag <<< succ)

downFrom :: forall a u. (Enum a, Unfoldable u) => a -> u a
downFrom = unfoldr (map diag <<< pred)

-- | Type class for finite enumerations. This should not be considered a part of
-- | a numeric hierarchy, ala Haskell. Rather, this is a type class for small,
-- | ordered sum types with statically-determined cardinality and the ability
-- | to easily compute successor and predecessor elements. e.g. `DayOfWeek`, etc.
-- |
-- | Laws:
-- |
-- | - ```succ bottom >>= succ >>= succ ... succ [cardinality - 1 times] == top```
-- | - ```pred top >>= pred >>= pred ... pred [cardinality - 1 times] == bottom```
-- | - ```forall a > bottom: pred a >>= succ == Just a```
-- | - ```forall a < top: succ a >>= pred == Just a```
-- | - ```forall a > bottom: fromEnum <$> pred a = Just (fromEnum a - 1)```
-- | - ```forall a < top: fromEnum <$> succ a = Just (fromEnum a + 1)```
-- | - ```e1 `compare` e2 == fromEnum e1 `compare` fromEnum e2```
-- | - ```toEnum (fromEnum a) = Just a```

class (Bounded a, Enum a) <= BoundedEnum a where
cardinality :: Cardinality a
toEnum :: Int -> Maybe a
fromEnum :: a -> Int

-- | Runs in `O(n)` where `n` is `fromEnum top`
-- |
-- | ```defaultCardinality = cardinality```
defaultCardinality :: forall a. (Bounded a, Enum a) => Cardinality a
defaultCardinality = Cardinality $ defaultCardinality' one (bottom :: a) where
defaultCardinality' i = maybe i (defaultCardinality' $ i + one) <<< succ

-- | Runs in `O(n)` where `n` is `fromEnum a`
-- |
-- | ```defaultToEnum succ bottom = toEnum```
-- TODO do we need to pass in bottom? It's a superclass if yes then we should pass it in for defaultCardinality too
defaultToEnum :: forall a. (a -> Maybe a) -> a -> (Int -> Maybe a)
defaultToEnum succ' bottom' n | n < zero = Nothing
| n == zero = Just bottom'
| otherwise = defaultToEnum succ' bottom' (n - one) >>= succ'

-- | Runs in `O(n)` where `n` is `fromEnum a`
-- |
-- | ```defaultFromEnum pred = fromEnum```
defaultFromEnum :: forall a. (a -> Maybe a) -> (a -> Int)
defaultFromEnum pred' e = maybe zero (\prd -> defaultFromEnum pred' prd + one) (pred' e)

-- | ## Instances

instance enumUnit :: Enum Unit where
succ = const Nothing
pred = const Nothing

instance boundedEnumUnit :: BoundedEnum Unit where
cardinality = Cardinality 1
toEnum 0 = Just unit
toEnum _ = Nothing
fromEnum = const 0

instance enumChar :: Enum Char where
cardinality = Cardinality 65536
succ = defaultSucc charToEnum charFromEnum
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 :: Int -> Maybe Char
Expand All @@ -125,30 +154,40 @@ charToEnum _ = Nothing
charFromEnum :: Char -> Int
charFromEnum = toCharCode

instance enumMaybe :: (Enum a) => Enum (Maybe a) where
cardinality = maybeCardinality cardinality
succ Nothing = Just $ bottom
instance boundedEnumChar :: BoundedEnum Char where
cardinality = Cardinality 65536
toEnum = charToEnum
fromEnum = charFromEnum

-- TODO JB BoundedEnum is too restrictive on a, all we need is bottom
instance enumMaybe :: (BoundedEnum a) => Enum (Maybe a) where
succ Nothing = Just $ Just bottom
succ (Just a) = Just <$> succ a
pred Nothing = Nothing
pred (Just a) = Just <$> pred a
pred (Just a) = Just $ pred a

instance boundedEnumMaybe :: (BoundedEnum a) => BoundedEnum (Maybe a) where
cardinality = maybeCardinality cardinality
toEnum = maybeToEnum cardinality
fromEnum Nothing = zero
fromEnum (Just e) = fromEnum e + one

maybeToEnum :: forall a. (Enum a) => Cardinality a -> Int -> Maybe (Maybe a)
maybeToEnum :: forall a. (BoundedEnum a) => Cardinality a -> Int -> Maybe (Maybe a)
maybeToEnum carda n | n <= runCardinality (maybeCardinality carda) =
if n == zero
then Just $ Nothing
else Just $ toEnum (n - one)
maybeToEnum _ _ = Nothing

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

instance enumBoolean :: Enum Boolean where
cardinality = Cardinality 2
succ = booleanSucc
pred = booleanPred

instance boundedEnumBoolean :: BoundedEnum Boolean where
cardinality = Cardinality 2
toEnum = defaultToEnum booleanSucc bottom
fromEnum = defaultFromEnum booleanPred

Expand All @@ -160,43 +199,47 @@ booleanPred :: Boolean -> Maybe Boolean
booleanPred true = Just false
booleanPred _ = Nothing

instance enumTuple :: (Enum a, Enum b) => Enum (Tuple a b) where
cardinality = tupleCardinality cardinality cardinality
instance enumTuple :: (Enum a, BoundedEnum b) => Enum (Tuple a b) where
succ (Tuple a b) = maybe (flip Tuple bottom <$> succ a) (Just <<< Tuple a) (succ b)
pred (Tuple a b) = maybe (flip Tuple bottom <$> pred a) (Just <<< Tuple a) (pred b)
pred (Tuple a b) = maybe (flip Tuple top <$> pred a) (Just <<< Tuple a) (pred b)

instance boundedEnumTuple :: (BoundedEnum a, BoundedEnum b) => BoundedEnum (Tuple a b) where
cardinality = tupleCardinality cardinality cardinality
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 -> Int -> Maybe (Tuple a b)
tupleToEnum :: forall a b. (BoundedEnum a, BoundedEnum b) => Cardinality b -> Int -> Maybe (Tuple a b)
tupleToEnum cardb n = Tuple <$> (toEnum (n / (runCardinality cardb))) <*> (toEnum (n `mod` (runCardinality cardb)))

tupleFromEnum :: forall a b. (Enum a, Enum b) => Cardinality b -> Tuple a b -> Int
tupleFromEnum :: forall a b. (BoundedEnum a, BoundedEnum b) => Cardinality b -> Tuple a b -> Int
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)

instance enumEither :: (Enum a, Enum b) => Enum (Either a b) where
cardinality = eitherCardinality cardinality cardinality
instance enumEither :: (BoundedEnum a, BoundedEnum b) => Enum (Either a b) where
succ (Left a) = maybe (Just $ Right bottom) (Just <<< Left) (succ a)
succ (Right b) = maybe (Nothing) (Just <<< Right) (succ b)
pred (Left a) = maybe (Nothing) (Just <<< Left) (pred a)
pred (Right b) = maybe (Just $ Left top) (Just <<< Right) (pred b)

instance boundedEnumEither :: (BoundedEnum a, BoundedEnum b) => BoundedEnum (Either a b) where
cardinality = eitherCardinality cardinality cardinality
toEnum = eitherToEnum cardinality cardinality
fromEnum = eitherFromEnum cardinality

eitherToEnum :: forall a b. (Enum a, Enum b) => Cardinality a -> Cardinality b -> Int -> Maybe (Either a b)
eitherToEnum :: forall a b. (BoundedEnum a, BoundedEnum b) => Cardinality a -> Cardinality b -> Int -> Maybe (Either a b)
eitherToEnum carda cardb n =
if n >= zero && 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 -> Int)
eitherFromEnum :: forall a b. (BoundedEnum a, BoundedEnum b) => Cardinality a -> (Either a b -> Int)
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 :: forall a b. (BoundedEnum a, BoundedEnum b) => Cardinality a -> Cardinality b -> Cardinality (Either a b)
eitherCardinality l r = Cardinality $ (runCardinality l) + (runCardinality r)

0 comments on commit 51f7c06

Please sign in to comment.