右应用组合运算符的使用是否暗示可以使用 contramap?

问题描述

长话短说

当我发现自己在编写像 contramap 这样的代码时,我是否应该考虑使用 (. f) . g,我在徘徊,其中 f 实际上是将第二个参数预处理为 g .

长话短说

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

最初,我有两个输入,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' 的第 2 个和第 3 个参数,所以我在徘徊是否是使用 contramap 的合适时机。

解决方法

lmap f . grather than contramap,因为它也需要一个 Op 包装器)在清晰度方面确实比 (. f) . g 有所改进。如果您的读者熟悉 Profunctor,看到 lmap 会立即暗示正在修改某些内容的输入,而无需他们在脑海中执行点管道。请注意,这还不是一个广泛使用的习语。 (作为参考,这里是针对 the lmap versionthe dot section one 的 Serokell Hackage 搜索查询。)

至于你更长的例子,惯用的做法可能不是把它写成pointfree。也就是说,我们可以通过更改 fun/fun' 的参数顺序来获得更易读的 pointfree 版本,以便您可以使用 equivalent Applicative 实例而不是 { {1}} 一:

Monad

Pointfree 函数 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 函数 (<*>) 更容易理解,因为它的两个参数是相关函数函子中的计算。此外,此更改消除了对点部分技巧的需要。最后,由于 (=<<) 是函数的 (.),我们可以进一步将 fmap 改写为...

finalFun

... 从而得到一个 applicative 风格的表达,在我看来(不是很流行!),这是使用 applicative 函数的一种合理可读的方式。 (我们可能会使用 finalFun = uncurry fun' <$> both reduce <*> uncurry binOp 进一步简化它,但我觉得在这种特定情况下,发生的事情不太明显。)