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?

Was it helpful?

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.

  1. Pure then pure. Use regular function composition (.).

     g :: a -> b
     f :: b -> c
     f . g :: a -> c
    
  2. Pure then monadic. Also use (.).

     g :: a -> b
     f :: b -> m c
     f . g :: a -> m c
    
  3. Monadic then monadic. Use kleisli composition (<=<).

     g :: a -> m b
     f :: b -> m c
     f <=< g :: a -> m c
    
  4. 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
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top