Skip to content
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

Make sure default impls for BoundedEnum are TCO'd #37

Merged
merged 2 commits into from
Jan 31, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
30 changes: 22 additions & 8 deletions src/Data/Enum.purs
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,11 @@ defaultPred toEnum' fromEnum' a = toEnum' (fromEnum' a - 1)
-- |
-- | Runs in `O(n)` where `n` is `fromEnum top`
defaultCardinality :: forall a. Bounded a => Enum a => Cardinality a
defaultCardinality = Cardinality $ defaultCardinality' 1 (bottom :: a)
where
defaultCardinality' i = maybe i (defaultCardinality' (i + 1)) <<< succ
defaultCardinality = Cardinality $ go 1 (bottom :: a) where
go i x =
case succ x of
Just x' -> go (i + 1) x'
Nothing -> i

-- | Provides a default implementation for `toEnum`.
-- |
Expand All @@ -279,10 +281,18 @@ defaultCardinality = Cardinality $ defaultCardinality' 1 (bottom :: a)
-- |
-- | Runs in `O(n)` where `n` is `fromEnum a`.
defaultToEnum :: forall a. Bounded a => Enum a => Int -> Maybe a
defaultToEnum n
| n < 0 = Nothing
| n == 0 = Just bottom
| otherwise = defaultToEnum (n - 1) >>= succ
defaultToEnum i' =
if i' < 0
then Nothing
else go i' bottom
where
go i x =
if i == 0
then Just x
-- We avoid using >>= here because it foils tail-call optimization
else case succ x of
Just x' -> go (i - 1) x'
Nothing -> Nothing

-- | Provides a default implementation for `fromEnum`.
-- |
Expand All @@ -291,7 +301,11 @@ defaultToEnum n
-- |
-- | Runs in `O(n)` where `n` is `fromEnum a`.
defaultFromEnum :: forall a. Enum a => a -> Int
defaultFromEnum = maybe 0 (\prd -> defaultFromEnum prd + 1) <<< pred
defaultFromEnum = go 0 where
go i x =
case pred x of
Just x' -> go (i + 1) x'
Nothing -> i

diag :: forall a. a -> Tuple a a
diag a = Tuple a a
Expand Down
43 changes: 42 additions & 1 deletion test/Test/Data/Enum.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ module Test.Data.Enum (testEnum) where

import Prelude

import Data.Enum (class BoundedEnum, class Enum, defaultCardinality, defaultFromEnum, defaultToEnum, downFrom, downFromIncluding, enumFromThenTo, enumFromTo, upFrom, upFromIncluding)
import Data.Enum (class BoundedEnum, class Enum, Cardinality, defaultCardinality, defaultFromEnum, defaultToEnum, downFrom, downFromIncluding, enumFromThenTo, enumFromTo, upFrom, upFromIncluding)
import Data.Maybe (Maybe(..))
import Data.Newtype (unwrap)
import Data.NonEmpty ((:|))
import Effect (Effect)
import Effect.Console (log)
Expand Down Expand Up @@ -44,6 +45,28 @@ instance boundedEnumT :: BoundedEnum T where
toEnum = defaultToEnum
fromEnum = defaultFromEnum

-- | A newtype over Int which is supposed to represent Ints bounded between 0
-- | and 10,000. Why 10,000? It's just about large enough that we'll most
-- | likely see stack overflow errors if we've managed to break TCO.
newtype Upto10k = Upto10k Int

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen stack-unsafe code work up to 30k iterations in the past. I always do 100k.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, news to me - good to know, thanks! I'll change this.


derive newtype instance eqUpto10k :: Eq Upto10k
derive newtype instance ordUpto10k :: Ord Upto10k
derive newtype instance showUpto10k :: Show Upto10k

instance boundedUpto10k :: Bounded Upto10k where
top = Upto10k 10000
bottom = Upto10k 0

instance enumUpto10k :: Enum Upto10k where
succ (Upto10k x) = if (x+1) > 10000 then Nothing else Just (Upto10k (x+1))
pred (Upto10k x) = if (x-1) < 0 then Nothing else Just (Upto10k (x-1))

instance boundedEnumUpto10k :: BoundedEnum Upto10k where
cardinality = defaultCardinality
toEnum = defaultToEnum
fromEnum = defaultFromEnum

testEnum :: Effect Unit
testEnum = do
log "enumFromTo"
Expand Down Expand Up @@ -153,3 +176,21 @@ testEnum = do
{ actual: downFromIncluding A
, expected: [A]
}

log "defaultCardinality is stack safe"
assertEqual
{ actual: unwrap (defaultCardinality :: Cardinality Upto10k)
, expected: 10001
}

log "defaultToEnum is stack safe"
assertEqual
{ actual: defaultToEnum 10000
, expected: Just (Upto10k 10000)
}

log "defaultFromEnum is stack safe"
assertEqual
{ actual: defaultFromEnum (Upto10k 10000)
, expected: 10000
}