Search code examples
haskellmonadsfunction-composition

How do I organize my pure functions with my monadic actions idiomatically


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.

    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.