首页 > 解决方案 > 右应用合成运算符的使用是否暗示可以使用 contramap?

问题描述

长话短说

contramap当我发现自己编写类似的代码时,如果我应该考虑使用,我正在徘徊,实际上在(. f) . g哪里预处理第二个参数。fg

长话短说

我将描述我是如何想出让我想到标题中的问题的代码的。

最初,我有两个输入a1 :: Ina2 :: In,包裹在一对(a1, a2) :: (In,In)中,我需要对这些输入进行两次交互处理。具体来说,我有一个binOp :: In -> In -> Mid生成“临时”结果的函数,以及一个输入fun :: Mid -> In -> In -> Out输出binOp函数。

鉴于上面“由另一个函数的输入和输出提供的函数”部分,我想使用函数 monad,所以我想出了这个,

finalFun = uncurry . fun =<< uncurry binOp

读起来并不复杂:binOp将输入作为一对,然后将其输出和其输入传递给fun,它也将输入作为一对。

但是,我注意到在实现中fun我实际上只使用了输入的“简化”版本,即我有一个类似的定义fun a b c = fun' a (reduce b) (reduce c),所以我认为fun我可以在;的fun'定义中使用 我想出了reducefinalFun

finalFun = (. both reduce) . uncurry . fun' =<< uncurry binOp

我相信,这远不那么容易阅读,尤其是因为它具有不自然的部分顺序。我只能考虑使用一些更具描述性的名称,例如

finalFun = preReduce . uncurry . fun' =<< uncurry binOp
    where preReduce = (. both reduce)

由于preReduce实际上是对 的第二个和第三个参数进行预处理fun',因此我在徘徊是否适合使用contramap.

标签: haskellfunctional-programmingidiomspointfree

解决方案


lmap f . g而不是contramap,因为这也需要一个Op包装器)在清晰度方面可能确实是一个改进(. f) . g。如果您的读者熟悉Profunctor,seeinglmap会立即提示对某些内容的输入正在修改,而无需他们在头脑中执行点探测。请注意,虽然它还不是一个普遍的习语。(作为参考,这里是版本点部分lmapSerokell Hackage Search 查询。)

至于您更长的示例,惯用的做法可能不是无意义地编写它。fun也就是说,我们可以通过更改/的参数顺序来获得更易读的无点版本fun',这样您就可以使用等效 Applicative实例而不是Monad一个:

binOp :: In -> In -> Mid
fun' :: In -> In -> Mid -> Out
reduce :: In -> In

both f = bimap f f

finalFun :: (In, In) -> Out
finalFun = uncurry fun' . both reduce <*> uncurry binOp

Pointfree function(<*>)可以说比 pointfree function 更容易理解(=<<),因为它的两个参数是相关函数仿函数中的计算。此外,此更改消除了对点部分技巧的需要。最后,既然(.)fmap函数,我们可以进一步改写finalFun为......

finalFun = uncurry fun' <$> both reduce <*> uncurry binOp

...因此获得了一种应用风格的表达方式,在我(不是那么流行!)看来,这是一种使用函数 applicative 的合理可读的方式。(我们可能会使用 进一步简化它liftA2,但我觉得在这种特定情况下,发生的事情不太明显。)


推荐阅读