如何在范畴编程中,将纯函数和单子操作组织起来?

17

今天我决定修复一些不必要运行在单子操作中的纯函数。以下是我的代码。

flagWorkDays :: [C.Day] -> Handler [WorkDay] 
flagWorkDays dayList =
   flagWeekEnds dayList >>=
   flagHolidays >>=
   flagScheduled >>=
   flagASAP >>=
   toWorkDays

这是 flagWeekEnds,目前的版本。

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 遵循类似的模式。 toWorkDays 只是将一种类型改变为另一种类型,并且是一个纯函数。

flagScheduledflagASAP 是单子动作。我不确定如何在 flagWorkDays 中习惯地结合单子动作和纯函数。 能否有人帮我修复flagWorkDays,假设 flagWeekEndsflagHolidays 已被变成纯函数?

3个回答

28

让我们先退一步。你有两种类型的函数,一些是形式为a -> b的纯函数,另一些是形式为a -> m b的单子函数。

为了避免混淆,我们也要坚持从右到左的组合方式。如果你更喜欢从左到右阅读,只需反转函数顺序,并将(<=<)替换为(>=>),将(.)替换为(>>>),并使用Control.Arrow库。

然后,这些函数可以通过四种方式组合。

  1. 纯 function 然后纯 function。 使用普通函数组合(.)

 g :: a -> b
 f :: b -> c
 f . g :: a -> c
  • 纯函数和单子函数。同时使用(.)

  •  g :: a -> b
     f :: b -> m c
     f . g :: a -> m c
    
  • 单子再单子。使用Kleisli组合(<=<)

  •  g :: a -> m b
     f :: b -> m c
     f <=< g :: a -> m c
    
  • 单子函数再纯函数。使用fmap对纯函数进行操作,然后使用 (.) 进行组合。

  •  g :: a -> m b
     f :: b -> c
     fmap f . g :: a -> m c
    
    忽略类型的具体细节,你的函数如下:
    flagWeekEnds :: a -> b
    flagHolidays :: b -> c
    flagScheduled :: c -> m d
    flagASAP :: d -> m e
    toWorkDays :: e -> f
    

    让我们从头开始。 flagWeekEndsflagHolidays 都是纯函数。情况1。

    flagHolidays . flagWeekEnds
      :: a -> c
    

    这是纯函数。接下来是 flagScheduled ,它是单子函数。情况2。

    flagScheduled . flagHolidays . flagWeekEnds
      :: a -> m d
    

    接下来是flagASAP,现在我们有两个单子函数。第三种情况。

    flagASAP <=< flagScheduled . flagHolidays . flagWeekEnds
      :: a -> m e
    

    最后,我们有纯函数toWorkDays。Case 4.

    fmap toWorkDays . flagASAP <=< flagScheduled . flagHolidays . flagWeekEnds
      :: a -> m f
    

    我们完成了。


    4
    这并不是很难。你只需要用(.)替换(>>=)并交换操作数的顺序。使用do语法可以帮助澄清。我还使用了Kleisli组合子(fish)(<=<) :: (b -> m c) -> (a -> m b) -> a -> m c将示例变成了pointfree形式,它本质上就是单子的(.)
    import Control.Monad
    
    flagWorkDays :: [C.Day] -> Handler [WorkDay] 
    flagWorkDays =
      fmap toWorkDays . flagASAP <=< flagScheduled . flagHolidays . flagWeekEnds
    

    @Tarrasch 无意义地浪费了。我忘记删除额外的参数。 - fuz
    有没有 flip(.) 的通常名称/预定义函数?我有点难过,因为这里的转换不得不把事情搞反了。 - hugomg
    @missingno 我还没有看到一个。 - fuz
    5
    @missingno >>>是从Control.Arrow模块中引入的吗?(尽管它有一个更通用的类型。) - dave4420

    4
    为了补充FUZxxl的答案,让我们简化flagWeekEnds:
    flagWeekEnds :: [C.Day] -> [(C.Day,Availability)]
    flagWeekEnds days = days `zip` map (flagAvailability . isWorkDay) days
    

    当变量是列表时,你通常在变量名后面加上“s”(例如:day -> days),这就像英语中的复数形式。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接