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
Church-encoded Empty & Error carriers #203
Conversation
[ bench "Either" $ whnf (errorLoop :: Int -> Either Int ()) n | ||
, bgroup "Identity" | ||
[ bench "Church.ErrorC" $ whnf (run . Church.runError @Int (pure . Left) (pure . Right) . errorLoop) n | ||
, bench "Either.ErrorC" $ whnf (run . Either.runError @Int . errorLoop) n | ||
, bench "ExceptT" $ whnf (run . Except.runExceptT @Int . errorLoop) n | ||
] | ||
, bgroup "IO" | ||
[ bench "Church.ErrorC IO" $ whnfAppIO (Church.runError @Int (pure . Left) (pure . Right) . errorLoop) n | ||
, bench "Either.ErrorC IO" $ whnfAppIO (Either.runError @Int . errorLoop) n | ||
, bench "ExceptT IO" $ whnfAppIO (Except.runExceptT @Int . errorLoop) n | ||
] | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Benchmark results (-O2
)
benchmarked Error/Either time 21.15 ms (20.36 ms .. 21.63 ms) 0.996 R² (0.992 R² .. 1.000 R²) mean 22.92 ms (22.33 ms .. 24.41 ms) std dev 1.845 ms (637.7 μs .. 3.254 ms) variance introduced by outliers: 34% (moderately inflated)
benchmarked Error/Identity/Church.ErrorC
time 47.47 ms (46.94 ms .. 47.98 ms)
1.000 R² (0.999 R² .. 1.000 R²)
mean 48.72 ms (48.17 ms .. 49.94 ms)
std dev 1.672 ms (858.0 μs .. 2.869 ms)benchmarked Error/Identity/Either.ErrorC
time 33.05 ms (32.78 ms .. 33.24 ms)
1.000 R² (0.999 R² .. 1.000 R²)
mean 34.26 ms (33.83 ms .. 35.11 ms)
std dev 1.253 ms (722.1 μs .. 1.877 ms)
variance introduced by outliers: 12% (moderately inflated)benchmarked Error/Identity/ExceptT
time 33.26 ms (32.81 ms .. 33.78 ms)
0.999 R² (0.999 R² .. 1.000 R²)
mean 34.74 ms (34.17 ms .. 36.11 ms)
std dev 1.778 ms (668.9 μs .. 3.034 ms)
variance introduced by outliers: 13% (moderately inflated)benchmarked Error/IO/Church.ErrorC IO
time 45.44 ms (44.99 ms .. 46.05 ms)
1.000 R² (0.999 R² .. 1.000 R²)
mean 46.82 ms (46.32 ms .. 47.98 ms)
std dev 1.433 ms (704.4 μs .. 2.344 ms)benchmarked Error/IO/Either.ErrorC IO
time 47.49 ms (46.70 ms .. 48.86 ms)
0.998 R² (0.994 R² .. 1.000 R²)
mean 49.15 ms (48.31 ms .. 51.28 ms)
std dev 2.210 ms (1.225 ms .. 3.408 ms)
variance introduced by outliers: 14% (moderately inflated)
benchmarked Error/IO/ExceptT IO
time 45.32 ms (44.81 ms .. 45.74 ms)
1.000 R² (0.999 R² .. 1.000 R²)
mean 47.76 ms (46.79 ms .. 49.27 ms)
std dev 2.341 ms (1.314 ms .. 3.400 ms)
variance introduced by outliers: 14% (moderately inflated)
As with all benchmarks, these can be assumed to be lies, but there are a couple of interesting takeaways nonetheless:
-
You can’t really beat
Either
because there’s just less for the simplifier to work with. So it’s not necessarily thatEither
is faster; it’s probably more so that there’s just less code because it’s not a monad transformer. (I’d like to confirm this with a non-transformer Church-encodedEither
, but that’s out of scope for this PR.) -
ghc
can “see through”Identity
more easily withEither.ErrorC
/ExceptT
than withChurch.ErrorC
. -
Church.ErrorC
does a little bit better composed ontoIO
than doesEither.ErrorC
/ExceptT
; this says to me that modulo cases where the simplifier can “see through” some of the types (as withIdentity
),Church.ErrorC
is slightly faster on average. -
-O
vs.-O2
made a small but noticeable difference.
-- 'runEmpty' ('pure' a) = 'Just' a | ||
-- 'runEmpty' ('pure' a) = 'pure' ('Just' a) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Whoops!
-- @ | ||
-- 'runError' j k ('pure' a) = k a | ||
-- @ | ||
-- @ | ||
-- 'runError' j k ('throwError' e) = j e | ||
-- @ | ||
-- @ | ||
-- 'runError' j k ('throwError' e \`'catchError'\` 'pure') = k e | ||
-- @ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m really happy with how these properties capture the type’s behaviour.
-- 'runError' ('throwError' e `catchError` 'pure') = 'pure' ('Right' e) | ||
-- 'runError' ('throwError' e \`'catchError'\` 'pure') = 'pure' ('Right' e) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve been meaning to fix this for a while.
-- @ | ||
-- runNonDet fork leaf nil ('pure' a '<|>' 'empty') = leaf a \`fork\` nil | ||
-- @ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I love how this single property completely encapsulates NonDetC
’s behaviour w.r.t. its three continuations and the operators they interpret..
liftA2 f (ReaderC a) (ReaderC b) = ReaderC $ \ r -> | ||
liftA2 f (a r) (b r) | ||
{-# INLINE liftA2 #-} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve implemented liftA2
for a few carriers.
runState :: forall s m a . Applicative m => s -> StateC s m a -> m (s, a) | ||
runState s (StateC m) = m (\ a s -> pure (s, a)) s | ||
runState :: forall s m a b . (s -> a -> m b) -> s -> StateC s m a -> m b | ||
runState f s (StateC m) = m f s |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s not super important to me that different carriers for the same effect provide exactly the same interface w.r.t. their handlers, and this definition of runState
for the Church-encoded StateC
is much more flexible.
This PR defines carriers for
Empty
&Error
implemented as Church-encoded monad transformers à la Codensity.Error
carrier.Error
effect.