$ replicateM_ 100000000 (Just ()) This gets compiled to a tight loop: $ ./test +RTS -s Just () 52,104 bytes allocated in the heap MUT time 0.007s ( 0.008s elapsed) GC time 0.000s ( 0.001s elapsed) %GC time 2.0% (10.5% elapsed)
=> Int -> m a -> m Unit replicateM_ 0 _ = pure unit replicateM_ n x = x *> replicateM_ (n - 1) x This fails quickly with RangeError: Maximum call stack size exceeded (demo)
m => Int -> m a -> m Unit replicateM_ n x = loop (pure unit) n where loop :: m Unit -> Int -> m Unit loop acc 0 = acc loop acc n = loop (x *> acc) (n - 1)
> replicateM_ 1000000 (Just 42) Just unit but still fails for others, like Eff : > replicateM_ 1000000 (log "testing") RangeError: Maximum call stack size exceeded
of one argument: tailRec :: ∀ a b. (a -> Step a b) -> a -> b This can be used to write variants with multiple arguments: tailRec2 :: ∀ a b c . (a -> b -> Step { fst :: a, snd :: b } c) -> a -> b -> c
:: ∀ m a. Monad m => Int -> m a -> m Unit replicateM_ n x = tailRec2 loop (pure unit) n where loop :: m Unit -> Int -> Step { fst :: m Unit, snd :: Int } (m Unit) loop acc 0 = Done acc loop acc n = Loop { fst: x *> acc, snd: n - 1 } Of course, this doesn't solve the problem, yet
ExceptT (m (Either e a)) instance MonadRec m => MonadRec (ExceptT e m) where tailRecM f = ExceptT <<< tailRecM \a -> case f a of ExceptT m -> m >>= \m' -> pure case m' of Left e -> Done (Left e) Right (Loop a1) -> Loop a1 Right (Done b) -> Done (Right b)
replicateM_ :: ∀ m a. MonadRec m => Int -> m a -> m Unit replicateM_ n x = tailRecM loop n where loop :: Int -> m (Step Int Unit) loop 0 = pure (Done unit) loop n = x $> Loop (n - 1) This is stack-safe for any law-abiding MonadRec instance! We can also implement other functions like mapM and foldM .
Free f a = Pure a | Impure (f (Free f a)) instance monadFree :: Functor f => Monad (Free f) liftFree :: ∀ f a. Functor f => f a -> Free f a liftFree fa = Impure (fmap Pure fa) represents sequences of instructions de ned by f .
a | Select Key (Maybe Value -> a) type Database = Free DatabaseF insert :: Key -> Value -> Database Unit insert k v = liftFree (Insert k v unit) select :: Key -> Database (Maybe Value) select k = liftFree (Select k id)
testDB :: ∀ a. Database a -> Test a testDB = runFree go where go :: DatabaseF (Database a) -> Test (Database a) go (Insert k v next) = do modify (insert k v) next go (Select k next) = do v <- gets (lookup k) next v
f a . MonadRec m => (f (Free f a) -> m (Free f a)) -> Free f a -> m a runFree can be written using tailRecM directly and uses a constant amount of stack.
for coroutines over some base monad. For example: data Emit o a = Emit o a type Producer o = FreeT (Emit o) Producer _ (Aff _) is useful for modelling asynchronous generators
b c . (a -> b -> c) -> f a -> g b -> h c fuse :: ∀ f g h m a . (Functor f, Functor g, Functor h, MonadRec m) => Fuse f g h -> FreeT f m a -> FreeT g m a -> FreeT h m a
a strict language like PureScript We trade off some instances for a safe implementation MonadRec has been implemented in PureScript, Scalaz, cats and fantasy-land. Check out the paper: functorial.com/stack-safety-for-free/index.pdf