How do I organize my pure functions with my monadic actions idiomatically
-
06-03-2021 - |
Question
I've decided today is the day I fix some of my pure functions that are unnecessarily running in a monadic action. Here's what I have.
flagWorkDays :: [C.Day] -> Handler [WorkDay]
flagWorkDays dayList =
flagWeekEnds dayList >>=
flagHolidays >>=
flagScheduled >>=
flagASAP >>=
toWorkDays
Here is flagWeekEnds, as of now.
flagWeekEnds :: [C.Day] -> Handler [(C.Day,Availability)]
flagWeekEnds dayList = do
let yepNope = Prelude.map isWorkDay dayList
availability = Prelude.map flagAvailability yepNope
return $ Prelude.zip dayList availability
flagHolidays
follows a similar pattern. toWorkDays
just changes one type to another, and is a pure function.
flagScheduled
, and flagASAP
are monadic actions. I am not sure how to combine the monadic actions with the pure functions idiomatically in flagWorkDays
. Could someone help me fix flagWorkDays
, assuming flagWeekEnds
and flagHolidays
have been made pure?
Solution
Let's take a step back for a moment. You have two types of functions, some pure with types of the form a -> b
, and some monadic of type a -> m b
.
To avoid confusion, let's also stick with right-to-left composition. If you prefer to read left-to-right, just reverse the order of the functions and replace (<=<)
with (>=>)
, and (.)
with (>>>)
from Control.Arrow
.
There are then four possibilities for how these can be composed.
Pure then pure. Use regular function composition
(.)
.g :: a -> b f :: b -> c f . g :: a -> c
Pure then monadic. Also use
(.)
.g :: a -> b f :: b -> m c f . g :: a -> m c
Monadic then monadic. Use kleisli composition
(<=<)
.g :: a -> m b f :: b -> m c f <=< g :: a -> m c
Monadic then pure. Use
fmap
on the pure function and(.)
to compose.g :: a -> m b f :: b -> c fmap f . g :: a -> m c
Ignoring the specifics of the types involved, your functions are:
flagWeekEnds :: a -> b
flagHolidays :: b -> c
flagScheduled :: c -> m d
flagASAP :: d -> m e
toWorkDays :: e -> f
Let's go from the top. flagWeekEnds
and flagHolidays
are both pure. Case 1.
flagHolidays . flagWeekEnds
:: a -> c
This is pure. Next up is flagScheduled
, which is monadic. Case 2.
flagScheduled . flagHolidays . flagWeekEnds
:: a -> m d
Next is flagASAP
, now we have two monadic functions. Case 3.
flagASAP <=< flagScheduled . flagHolidays . flagWeekEnds
:: a -> m e
And finally, we have the pure function toWorkDays
. Case 4.
fmap toWorkDays . flagASAP <=< flagScheduled . flagHolidays . flagWeekEnds
:: a -> m f
And we're done.
OTHER TIPS
To fill in FUZxxl's answer, let's pureify flagWeekEnds
:
flagWeekEnds :: [C.Day] -> [(C.Day,Availability)]
flagWeekEnds days = days `zip` map (flagAvailability . isWorkDay) days
You often put an "s" after variable names (day
-> days
) when its a list (as you do with plural in English).
It's not very difficult. You basically just replace (>>=)
by (.)
and flip the operands order. do
syntax may help to clarify. I also made the example pointfree using the Kleisli combinator (fish) (<=<) :: (b -> m c) -> (a -> m b) -> a -> m c
, that is essentially (.)
for monads.
import Control.Monad
flagWorkDays :: [C.Day] -> Handler [WorkDay]
flagWorkDays =
fmap toWorkDays . flagASAP <=< flagScheduled . flagHolidays . flagWeekEnds