From a9976fd1a44b895e15cc58066c398736c37023dd Mon Sep 17 00:00:00 2001 From: Joel Lefkowitz Date: Fri, 31 Jan 2025 11:47:03 +0000 Subject: [PATCH 1/7] Add ifM', when', whenM', unless' and unlessM' --- src/Control/Applicative.purs | 16 ++++++++++++++-- src/Control/Bind.purs | 21 +++++++++++++++++++++ src/Control/Monad.purs | 27 +++++++++++++++++++-------- src/Prelude.purs | 6 +++--- 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/src/Control/Applicative.purs b/src/Control/Applicative.purs index 6d444460..85b37119 100644 --- a/src/Control/Applicative.purs +++ b/src/Control/Applicative.purs @@ -3,14 +3,19 @@ module Control.Applicative , pure , liftA1 , unless + , unless' , when + , when' , module Control.Apply , module Data.Functor ) where import Control.Apply (class Apply, apply, (*>), (<*), (<*>)) +import Control.Category ((<<<)) +import Data.Boolean (otherwise) import Data.Functor (class Functor, map, void, ($>), (<#>), (<$), (<$>)) +import Data.HeytingAlgebra (not) import Data.Unit (Unit, unit) import Type.Proxy (Proxy(..)) @@ -64,7 +69,14 @@ when :: forall m. Applicative m => Boolean -> m Unit -> m Unit when true m = m when false _ = pure unit +-- | Perform an applicative action lazily when a condition is true. +when' :: forall m a. Applicative m => (a -> Boolean) -> (a -> m Unit) -> a -> m Unit +when' f m a = if f a then m a else pure unit + -- | Perform an applicative action unless a condition is true. unless :: forall m. Applicative m => Boolean -> m Unit -> m Unit -unless false m = m -unless true _ = pure unit +unless = when <<< not + +-- | Perform an applicative action lazily unless a condition is true. +unless' :: forall m a. Applicative m => (a -> Boolean) -> (a -> m Unit) -> a -> m Unit +unless' = when' <<< not diff --git a/src/Control/Bind.purs b/src/Control/Bind.purs index c6d8a6ae..313498d4 100644 --- a/src/Control/Bind.purs +++ b/src/Control/Bind.purs @@ -12,6 +12,7 @@ module Control.Bind , composeKleisliFlipped , (<=<) , ifM + , ifM' , module Data.Functor , module Control.Apply , module Control.Applicative @@ -148,3 +149,23 @@ infixr 1 composeKleisliFlipped as <=< -- | ``` ifM :: forall a m. Bind m => m Boolean -> m a -> m a -> m a ifM cond t f = cond >>= \cond' -> if cond' then t else f + +-- | Similar to `ifM` but for use in cases where one of the monadic actions may +-- | be expensive to compute or be responsible for side effects. As PureScript +-- | is not lazy, the standard `ifM` has to construct both monadic actions +-- | before returning the result, whereas here only the corresponding monadic +-- | action is constructed. +-- | +-- | ```purescript +-- | main :: Effect Unit +-- | main = do +-- | response <- ifM' exists update create user +-- | log response +-- | +-- | where +-- | create :: User -> Effect String +-- | update :: User -> Effect String +-- | exists :: User -> Effect Boolean +-- | ``` +ifM' :: forall a b m. Bind m => (a -> m Boolean) -> (a -> m b) -> (a -> m b) -> a -> m b +ifM' cond t f a = cond a >>= \cond' -> if cond' then t a else f a diff --git a/src/Control/Monad.purs b/src/Control/Monad.purs index 3d8400ae..76a1acb2 100644 --- a/src/Control/Monad.purs +++ b/src/Control/Monad.purs @@ -2,7 +2,9 @@ module Control.Monad ( class Monad , liftM1 , whenM + , whenM' , unlessM + , unlessM' , ap , module Data.Functor , module Control.Apply @@ -12,10 +14,13 @@ module Control.Monad import Control.Applicative (class Applicative, liftA1, pure, unless, when) import Control.Apply (class Apply, apply, (*>), (<*), (<*>)) -import Control.Bind (class Bind, bind, ifM, join, (<=<), (=<<), (>=>), (>>=)) +import Control.Bind (class Bind, bind, join, (<=<), (=<<), (>=>), (>>=), ifM, ifM') +import Control.Category ((>>>)) +import Data.HeytingAlgebra (not) +import Data.Function (($)) import Data.Functor (class Functor, map, void, ($>), (<#>), (<$), (<$>)) -import Data.Unit (Unit) +import Data.Unit (Unit, unit) import Type.Proxy (Proxy) -- | The `Monad` type class combines the operations of the `Bind` and @@ -55,16 +60,22 @@ liftM1 f a = do -- | Perform a monadic action when a condition is true, where the conditional -- | value is also in a monadic context. whenM :: forall m. Monad m => m Boolean -> m Unit -> m Unit -whenM mb m = do - b <- mb - when b m +whenM mb m = ifM mb m $ pure unit + +-- | Perform a monadic action lazily when a condition is true, where the conditional +-- | value is also in a monadic context. +whenM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit +whenM' mb m = ifM' mb m $ \_ -> pure unit -- | Perform a monadic action unless a condition is true, where the conditional -- | value is also in a monadic context. unlessM :: forall m. Monad m => m Boolean -> m Unit -> m Unit -unlessM mb m = do - b <- mb - unless b m +unlessM mb = whenM $ not <$> mb + +-- | Perform a monadic action lazily unless a condition is true, where the conditional +-- | value is also in a monadic context. +unlessM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit +unlessM' mb = whenM' $ \x -> mb x >>= not >>> pure -- | `ap` provides a default implementation of `(<*>)` for any `Monad`, without -- | using `(<*>)` as provided by the `Apply`-`Monad` superclass relationship. diff --git a/src/Prelude.purs b/src/Prelude.purs index 6f42076b..9a82f4fc 100644 --- a/src/Prelude.purs +++ b/src/Prelude.purs @@ -40,11 +40,11 @@ module Prelude , module Data.Void ) where -import Control.Applicative (class Applicative, pure, liftA1, unless, when) +import Control.Applicative (class Applicative, liftA1, pure, unless, unless', when, when') import Control.Apply (class Apply, apply, (*>), (<*), (<*>)) -import Control.Bind (class Bind, bind, class Discard, discard, ifM, join, (<=<), (=<<), (>=>), (>>=)) +import Control.Bind (class Bind, bind, class Discard, discard, ifM, ifM', join, (<=<), (=<<), (>=>), (>>=)) import Control.Category (class Category, identity) -import Control.Monad (class Monad, liftM1, unlessM, whenM, ap) +import Control.Monad (class Monad, ap, liftM1, unlessM, unlessM', whenM, whenM') import Control.Semigroupoid (class Semigroupoid, compose, (<<<), (>>>)) import Data.Boolean (otherwise) From 7b1405b03e27439098f420c8821484a639e42f10 Mon Sep 17 00:00:00 2001 From: Joel Lefkowitz Date: Fri, 31 Jan 2025 12:20:15 +0000 Subject: [PATCH 2/7] Update the changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7e5af83..54e4fb48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Breaking changes: New features: +- Add lazily evaluated versions of the conditional functions for applicatives + and monads ifM', when', whenM', unless' and unlessM' + Bugfixes: Other improvements: From 5467591eee7de2f57eff37a56206d9e1d8fc2e9c Mon Sep 17 00:00:00 2001 From: Joel Lefkowitz Date: Fri, 31 Jan 2025 12:21:57 +0000 Subject: [PATCH 3/7] Add PR id to the changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54e4fb48..24779ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ Breaking changes: New features: - Add lazily evaluated versions of the conditional functions for applicatives - and monads ifM', when', whenM', unless' and unlessM' + and monads ifM', when', whenM', unless' and unlessM' (#313) Bugfixes: From 7e6baad48827b597ebd1d8dfea69559110f29f57 Mon Sep 17 00:00:00 2001 From: Joel Lefkowitz Date: Fri, 31 Jan 2025 16:09:51 +0000 Subject: [PATCH 4/7] Fix ifM' docstring --- src/Control/Bind.purs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Control/Bind.purs b/src/Control/Bind.purs index 313498d4..3bc74d86 100644 --- a/src/Control/Bind.purs +++ b/src/Control/Bind.purs @@ -151,10 +151,9 @@ ifM :: forall a m. Bind m => m Boolean -> m a -> m a -> m a ifM cond t f = cond >>= \cond' -> if cond' then t else f -- | Similar to `ifM` but for use in cases where one of the monadic actions may --- | be expensive to compute or be responsible for side effects. As PureScript --- | is not lazy, the standard `ifM` has to construct both monadic actions --- | before returning the result, whereas here only the corresponding monadic --- | action is constructed. +-- | be expensive to compute. As PureScript is not lazy, the standard `ifM` has +-- | to construct both monadic actions before returning the result, whereas here +-- | only the corresponding monadic action is constructed. -- | -- | ```purescript -- | main :: Effect Unit From aaa6fe60b191582623b320db833aa6f9c7ca0023 Mon Sep 17 00:00:00 2001 From: Joel Lefkowitz Date: Wed, 5 Feb 2025 18:04:17 +0000 Subject: [PATCH 5/7] Simplify the ifM' docstring and remove superfluous operators --- src/Control/Bind.purs | 4 +--- src/Control/Monad.purs | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Control/Bind.purs b/src/Control/Bind.purs index 3bc74d86..b5574c9d 100644 --- a/src/Control/Bind.purs +++ b/src/Control/Bind.purs @@ -151,9 +151,7 @@ ifM :: forall a m. Bind m => m Boolean -> m a -> m a -> m a ifM cond t f = cond >>= \cond' -> if cond' then t else f -- | Similar to `ifM` but for use in cases where one of the monadic actions may --- | be expensive to compute. As PureScript is not lazy, the standard `ifM` has --- | to construct both monadic actions before returning the result, whereas here --- | only the corresponding monadic action is constructed. +-- | be expensive to construct. -- | -- | ```purescript -- | main :: Effect Unit diff --git a/src/Control/Monad.purs b/src/Control/Monad.purs index 76a1acb2..e4a0c822 100644 --- a/src/Control/Monad.purs +++ b/src/Control/Monad.purs @@ -65,7 +65,7 @@ whenM mb m = ifM mb m $ pure unit -- | Perform a monadic action lazily when a condition is true, where the conditional -- | value is also in a monadic context. whenM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit -whenM' mb m = ifM' mb m $ \_ -> pure unit +whenM' mb m = ifM' mb m \_ -> pure unit -- | Perform a monadic action unless a condition is true, where the conditional -- | value is also in a monadic context. @@ -75,7 +75,7 @@ unlessM mb = whenM $ not <$> mb -- | Perform a monadic action lazily unless a condition is true, where the conditional -- | value is also in a monadic context. unlessM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit -unlessM' mb = whenM' $ \x -> mb x >>= not >>> pure +unlessM' mb = whenM' \x -> mb x >>= not >>> pure -- | `ap` provides a default implementation of `(<*>)` for any `Monad`, without -- | using `(<*>)` as provided by the `Apply`-`Monad` superclass relationship. From f44a71407f2fa4dc3da58fa502eea1261381b448 Mon Sep 17 00:00:00 2001 From: Joel Lefkowitz Date: Thu, 6 Feb 2025 14:59:11 +0000 Subject: [PATCH 6/7] Remove extra import --- src/Control/Applicative.purs | 5 ++--- src/Control/Monad.purs | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Control/Applicative.purs b/src/Control/Applicative.purs index 85b37119..22ccec27 100644 --- a/src/Control/Applicative.purs +++ b/src/Control/Applicative.purs @@ -13,7 +13,6 @@ module Control.Applicative import Control.Apply (class Apply, apply, (*>), (<*), (<*>)) import Control.Category ((<<<)) -import Data.Boolean (otherwise) import Data.Functor (class Functor, map, void, ($>), (<#>), (<$), (<$>)) import Data.HeytingAlgebra (not) import Data.Unit (Unit, unit) @@ -69,7 +68,7 @@ when :: forall m. Applicative m => Boolean -> m Unit -> m Unit when true m = m when false _ = pure unit --- | Perform an applicative action lazily when a condition is true. +-- | Construct an applicative action when a condition is true. when' :: forall m a. Applicative m => (a -> Boolean) -> (a -> m Unit) -> a -> m Unit when' f m a = if f a then m a else pure unit @@ -77,6 +76,6 @@ when' f m a = if f a then m a else pure unit unless :: forall m. Applicative m => Boolean -> m Unit -> m Unit unless = when <<< not --- | Perform an applicative action lazily unless a condition is true. +-- | Construct an applicative action unless a condition is true. unless' :: forall m a. Applicative m => (a -> Boolean) -> (a -> m Unit) -> a -> m Unit unless' = when' <<< not diff --git a/src/Control/Monad.purs b/src/Control/Monad.purs index e4a0c822..22415c75 100644 --- a/src/Control/Monad.purs +++ b/src/Control/Monad.purs @@ -62,7 +62,7 @@ liftM1 f a = do whenM :: forall m. Monad m => m Boolean -> m Unit -> m Unit whenM mb m = ifM mb m $ pure unit --- | Perform a monadic action lazily when a condition is true, where the conditional +-- | Construct a monadic action when a condition is true, where the conditional -- | value is also in a monadic context. whenM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit whenM' mb m = ifM' mb m \_ -> pure unit @@ -72,7 +72,7 @@ whenM' mb m = ifM' mb m \_ -> pure unit unlessM :: forall m. Monad m => m Boolean -> m Unit -> m Unit unlessM mb = whenM $ not <$> mb --- | Perform a monadic action lazily unless a condition is true, where the conditional +-- | Construct a monadic action unless a condition is true, where the conditional -- | value is also in a monadic context. unlessM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit unlessM' mb = whenM' \x -> mb x >>= not >>> pure From 2260d19cb26f5c5209481e8b12b221546fa75806 Mon Sep 17 00:00:00 2001 From: Joel Lefkowitz Date: Sun, 23 Feb 2025 15:20:36 +0000 Subject: [PATCH 7/7] Clarify the docstrings for whenM' and unlessM' --- src/Control/Monad.purs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Control/Monad.purs b/src/Control/Monad.purs index 22415c75..f1b67773 100644 --- a/src/Control/Monad.purs +++ b/src/Control/Monad.purs @@ -62,8 +62,8 @@ liftM1 f a = do whenM :: forall m. Monad m => m Boolean -> m Unit -> m Unit whenM mb m = ifM mb m $ pure unit --- | Construct a monadic action when a condition is true, where the conditional --- | value is also in a monadic context. +-- | Perform a monadic action when a condition is true, without constructing it +-- | otherwise, where the conditional value is also in a monadic context. whenM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit whenM' mb m = ifM' mb m \_ -> pure unit @@ -72,8 +72,8 @@ whenM' mb m = ifM' mb m \_ -> pure unit unlessM :: forall m. Monad m => m Boolean -> m Unit -> m Unit unlessM mb = whenM $ not <$> mb --- | Construct a monadic action unless a condition is true, where the conditional --- | value is also in a monadic context. +-- | Perform a monadic action unless a condition is true, without constructing +-- | it otherwise, where the conditional value is also in a monadic context. unlessM' :: forall m a. Monad m => (a -> m Boolean) -> (a -> m Unit) -> a -> m Unit unlessM' mb = whenM' \x -> mb x >>= not >>> pure