From 5ac167d709635d83dd113b0accbfd6548b8abf63 Mon Sep 17 00:00:00 2001 From: Ryan Hendrickson Date: Mon, 16 May 2022 20:37:21 -0400 Subject: [PATCH] Add convenience functions `tailRec2` and `tailRec3` complete the `tailRec` and `tailRecM` matrix, and `loop2` and `loop3` are new conveniences for both `tailRec2/3` and `tailRecM2/3` that better abstract the consumer away from the details of the `a`, `b`, `c` record labels we use in those functions. --- CHANGELOG.md | 1 + src/Control/Monad/Rec/Class.purs | 34 ++++++++++++++++++++++++++++++++ test/Test/Main.purs | 11 ++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62ca8a6..a2674a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Notable changes to this project are documented in this file. The format is based Breaking changes: New features: +- Added `tailRec2`, `tailRec3`, `loop2`, `loop3` convenience functions (#40 by @rhendric) Bugfixes: diff --git a/src/Control/Monad/Rec/Class.purs b/src/Control/Monad/Rec/Class.purs index 4c24ddb..b80de6b 100644 --- a/src/Control/Monad/Rec/Class.purs +++ b/src/Control/Monad/Rec/Class.purs @@ -2,12 +2,16 @@ module Control.Monad.Rec.Class ( Step(..) , class MonadRec , tailRec + , tailRec2 + , tailRec3 , tailRecM , tailRecM2 , tailRecM3 , forever , whileJust , untilJust + , loop2 + , loop3 ) where import Prelude @@ -55,6 +59,9 @@ class Monad m <= MonadRec m where tailRecM :: forall a b. (a -> m (Step a b)) -> a -> m b -- | Create a tail-recursive function of two arguments which uses constant stack space. +-- | +-- | The `loop2` helper function provides a curried alternative to the `Loop` +-- | constructor for this function. tailRecM2 :: forall m a b c . MonadRec m @@ -65,6 +72,9 @@ tailRecM2 tailRecM2 f a b = tailRecM (\o -> f o.a o.b) { a, b } -- | Create a tail-recursive function of three arguments which uses constant stack space. +-- | +-- | The `loop3` helper function provides a curried alternative to the `Loop` +-- | constructor for this function. tailRecM3 :: forall m a b c d . MonadRec m @@ -93,6 +103,20 @@ tailRec f = go <<< f go (Loop a) = go (f a) go (Done b) = b +-- | Create a pure tail-recursive function of two arguments +-- | +-- | The `loop2` helper function provides a curried alternative to the `Loop` +-- | constructor for this function. +tailRec2 :: forall a b c. (a -> b -> Step { a :: a, b :: b } c) -> a -> b -> c +tailRec2 f a b = tailRec (\o -> f o.a o.b) { a, b } + +-- | Create a pure tail-recursive function of three arguments +-- | +-- | The `loop3` helper function provides a curried alternative to the `Loop` +-- | constructor for this function. +tailRec3 :: forall a b c d. (a -> b -> c -> Step { a :: a, b :: b, c :: c } d) -> a -> b -> c -> d +tailRec3 f a b c = tailRec (\o -> f o.a o.b o.c) { a, b, c } + instance monadRecIdentity :: MonadRec Identity where tailRecM f = Identity <<< tailRec (runIdentity <<< f) where runIdentity (Identity x) = x @@ -155,3 +179,13 @@ untilJust :: forall a m. MonadRec m => m (Maybe a) -> m a untilJust m = unit # tailRecM \_ -> m <#> case _ of Nothing -> Loop unit Just x -> Done x + +-- | A curried version of the `Loop` constructor, provided as a convenience for +-- | use with `tailRec2` and `tailRecM2`. +loop2 :: forall a b c. a -> b -> Step { a :: a, b :: b } c +loop2 a b = Loop { a, b } + +-- | A curried version of the `Loop` constructor, provided as a convenience for +-- | use with `tailRec3` and `tailRecM3`. +loop3 :: forall a b c d. a -> b -> c -> Step { a :: a, b :: b, c :: c } d +loop3 a b c = Loop { a, b, c } diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 749e97b..9cba369 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -2,7 +2,7 @@ module Test.Main where import Prelude -import Control.Monad.Rec.Class (Step(..), tailRec, tailRecM, tailRecM2, untilJust, whileJust) +import Control.Monad.Rec.Class (Step(..), tailRec, tailRec2, tailRecM, tailRecM2, untilJust, whileJust, loop2) import Data.Either (Either(..)) import Data.Maybe (Maybe(..)) import Effect (Effect) @@ -19,6 +19,12 @@ triangle = tailRecM2 f 0 log $ "Accumulator: " <> show acc pure (Loop { a: acc + n, b: n - 1 }) +trianglePure :: Int -> Int +trianglePure = tailRec2 f 0 + where + f acc 0 = Done acc + f acc n = loop2 (acc + n) (n - 1) + loop :: Int -> Effect Unit loop n = tailRecM go n where @@ -49,6 +55,9 @@ main :: Effect Unit main = do test "triangle" 55 do triangle 10 + + test "trianglePure" 55 do + pure $ trianglePure 10 test "mutual" false do pure $ mutual 1000001