Skip to content

Add lazily evaluated versions of the conditional functions for applicatives and monads #313

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

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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' (#313)

Bugfixes:

Other improvements:
Expand Down
16 changes: 14 additions & 2 deletions src/Control/Applicative.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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(..))

Expand Down Expand Up @@ -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
20 changes: 20 additions & 0 deletions src/Control/Bind.purs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Control.Bind
, composeKleisliFlipped
, (<=<)
, ifM
, ifM'
, module Data.Functor
, module Control.Apply
, module Control.Applicative
Expand Down Expand Up @@ -148,3 +149,22 @@ 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. As PureScript is not lazy, the standard `ifM` has
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps "construct" rather than "compute" in this first sentence? And really I think that would suffice for the explanation, elaborating further on strict eval semantics only really applies to people who are newly coming to PS from Haskell. 😉

Having the code example is still good though 👍

-- | 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
27 changes: 19 additions & 8 deletions src/Control/Monad.purs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ module Control.Monad
( class Monad
, liftM1
, whenM
, whenM'
, unlessM
, unlessM'
, ap
, module Data.Functor
, module Control.Apply
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down
6 changes: 3 additions & 3 deletions src/Prelude.purs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading