Created
December 26, 2019 10:37
-
-
Save monadplus/9da97f4a515d7738ec9987c5b6f32dc0 to your computer and use it in GitHub Desktop.
tailRecM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# LANGUAGE ExistentialQuantification #-} | |
{-# LANGUAGE InstanceSigs #-} | |
--{-# LANGUAGE ScopedTypeVariables #-} | |
module TailRecM where | |
-- This only makes sense in Purescript (strict haskell dialect) | |
-- But I am lacking a purescript runtime interpreter | |
import Control.Monad.Writer.Lazy | |
import Control.Monad.Trans.State.Lazy | |
import Data.Monoid (Product) | |
import Data.Functor.Identity | |
pow :: Int -> Int -> Int | |
pow n = go 1 | |
where | |
go acc 0 = acc | |
go acc p = go (acc * n) (p - 1) | |
-- Will blow the stack | |
powWriter :: Int -> Int -> Writer (Product Int) () | |
powWriter n = go | |
where | |
go :: Int -> Writer (Product Int) () | |
go 0 = pure () | |
go m = do | |
tell (Product n) | |
go (m - 1) | |
-- Generic tail-recursive function: | |
tailRec :: forall a b. (a -> Either a b) -> a -> b | |
tailRec f a = go (f a) | |
where | |
go (Left a) = go (f a) | |
go (Right b) = b | |
-- Pow stack-safe | |
powTR :: Int -> Int -> Int | |
powTR n p = tailRec go (1, p) | |
where | |
go (acc, 0) = Right acc | |
go (acc, m) = Left (acc * n, m - 1) | |
-- TailRec can be generalized to several monads | |
-- tailRec is a valid implementation for Id | |
class (Monad m) => MonadRec m where | |
-- A valid implementation of MonadRec must guarantee that the stack usage of tailRecM | |
-- is at most a constant multiple of the stack usage off itself. | |
tailRecM :: forall a b. (a -> m (Either a b)) -> a -> m b | |
-- Not a valid instance. Doesn't fulfill the law | |
tailRecM f a = f a >>= go | |
where | |
go (Left a) = f a >>= go | |
go (Right b) = return b | |
forever :: forall m a b. (MonadRec m) => m a -> m b | |
forever = undefined | |
-- Example of instances | |
instance (MonadRec m) => MonadRec (StateT s m) where | |
tailRecM f a = StateT (\s -> tailRecM f' (a, s)) | |
where | |
f' (a, s) = do | |
(m, s1) <- runStateT (f a) s | |
return $ case m of | |
Left a -> Left (a, s1) | |
Right b -> Right (b, s1) | |
instance (Monoid w, MonadRec m) => MonadRec (WriterT w m) where | |
tailRecM f a = WriterT $ tailRecM f' (a, mempty) | |
where | |
f' (a, w) = do | |
(m, w1) <- runWriterT (f a) | |
return $ case m of | |
Left a -> Left (a, w1) | |
Right b -> Right (b, w1) | |
instance MonadRec Identity where | |
tailRecM f a = Identity $ go (f a) | |
where | |
go (Identity m) = case m of | |
Left a -> go (f a) | |
Right b -> b | |
powWriterTR :: Int -> Int -> Writer (Product Int) () | |
powWriterTR n = tailRecM go | |
where | |
go :: Int -> Writer (Product Int) (Either Int ()) | |
go 0 = return (Right ()) | |
go p = do | |
tell $ Product n | |
return (Left $ p - 1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment