right-applied 组合运算符的使用是否暗示可以使用 contramap?
Does the usage of right-applied composition operator hint that contramap could be used?
长话短说
当我发现自己正在编写类似 (. f) . g
的代码时,我正在考虑是否应该考虑使用 contramap
,其中 f
实际上是在预处理 [=17] 的第二个参数=].
长话短说
我将描述我是如何想出让我想到标题中的问题的代码的。
最初,我有两个输入,a1 :: In
和 a2 :: 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)
这样的定义,所以我认为,在 finalFun
的定义中,我可以使用 fun'
和 reduce
来代替 fun
;我想到了
finalFun = (. both reduce) . uncurry . fun' =<< uncurry binOp
这远没有那么容易阅读,尤其是因为它具有 un-natural 部件顺序,我相信。我只能想到使用一些更具描述性的名称,如
finalFun = preReduce . uncurry . fun' =<< uncurry binOp
where preReduce = (. both reduce)
因为 preReduce
实际上是 pre-processing fun'
的第二个和第三个参数,我在想现在是否是使用 contramap
.[=35 的正确时机=]
lmap f . g
(, as that would also require an Op
wrapper) might indeed be an improvement over (. f) . g
in terms of clarity. If your readers are familiar with Profunctor
, seeing lmap
will promptly suggest the input to something is being modified, without the need for them to perform dot plumbing in their heads. Note it isn't a widespread idiom yet, though. (For reference, here are Serokell Hackage Search queries for the lmap
version and the dot section one.)
至于你更长的例子,惯用的做法可能不是将它写成 pointfree。也就是说,我们可以通过更改 fun
/fun'
的参数顺序来获得更具可读性的 pointfree 版本,这样您就可以使用 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 函数 (<*>)
可以说比 pointfree 函数 (=<<)
更容易理解,因为它的两个参数是相关函数函子中的计算。此外,此更改消除了对点部分技巧的需要。最后,由于 (.)
是函数的 fmap
,我们可以进一步将 finalFun
改写为...
finalFun = uncurry fun' <$> both reduce <*> uncurry binOp
... 从而得到一个应用风格的表达式,在我(不太流行!)看来,这是使用函数应用的一种合理可读的方式。 (我们可能会使用 liftA2
进一步简化它,但我觉得在这种特定情况下,发生的事情不太明显。)
长话短说
当我发现自己正在编写类似 (. f) . g
的代码时,我正在考虑是否应该考虑使用 contramap
,其中 f
实际上是在预处理 [=17] 的第二个参数=].
长话短说
我将描述我是如何想出让我想到标题中的问题的代码的。
最初,我有两个输入,a1 :: In
和 a2 :: 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)
这样的定义,所以我认为,在 finalFun
的定义中,我可以使用 fun'
和 reduce
来代替 fun
;我想到了
finalFun = (. both reduce) . uncurry . fun' =<< uncurry binOp
这远没有那么容易阅读,尤其是因为它具有 un-natural 部件顺序,我相信。我只能想到使用一些更具描述性的名称,如
finalFun = preReduce . uncurry . fun' =<< uncurry binOp
where preReduce = (. both reduce)
因为 preReduce
实际上是 pre-processing fun'
的第二个和第三个参数,我在想现在是否是使用 contramap
.[=35 的正确时机=]
lmap f . g
(Op
wrapper) might indeed be an improvement over (. f) . g
in terms of clarity. If your readers are familiar with Profunctor
, seeing lmap
will promptly suggest the input to something is being modified, without the need for them to perform dot plumbing in their heads. Note it isn't a widespread idiom yet, though. (For reference, here are Serokell Hackage Search queries for the lmap
version and the dot section one.)
至于你更长的例子,惯用的做法可能不是将它写成 pointfree。也就是说,我们可以通过更改 fun
/fun'
的参数顺序来获得更具可读性的 pointfree 版本,这样您就可以使用 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 函数 (<*>)
可以说比 pointfree 函数 (=<<)
更容易理解,因为它的两个参数是相关函数函子中的计算。此外,此更改消除了对点部分技巧的需要。最后,由于 (.)
是函数的 fmap
,我们可以进一步将 finalFun
改写为...
finalFun = uncurry fun' <$> both reduce <*> uncurry binOp
... 从而得到一个应用风格的表达式,在我(不太流行!)看来,这是使用函数应用的一种合理可读的方式。 (我们可能会使用 liftA2
进一步简化它,但我觉得在这种特定情况下,发生的事情不太明显。)