我已经在一个算法中工作了一段时间,该算法需要编写它的函数部分,以便合成的返回值包含两个函数的输出。例如(在JavaScript中):
const double = x => x * 2
const increment = x => x + 1
const doubleThenIncrement = composeConservingOutputs(increment, double)
doubleThenIncrement(2) // => [4, 5]
我一直想知道这是否是一种已知的构图模式。在Haskell中,这似乎是以一种干净的方式实现的,可以将一个箭头的输出连接到另一个箭头的扇出和id:
import Control.Arrow
double = (*) 2
increment = (+) 1
doubleThenIncrement = arr double >>> (arr id &&& arr increment)
但我无法找到这种构图是否是一种常见/已知的模式。
我发现这很有用,因为它允许非常容易地检查中间函数的输出以进行调试,这让我觉得这可能已经是一件事了。
Hoogle search for the pattern (a -> b) -> (b -> c) -> (a -> (b, c))证明无效。
由于
答案 0 :(得分:2)
如果没有其他导入,您可以使用
doubleThenIncrement = fmap increment . (id >>= (,)) . double
此函数首先将其参数加倍,然后使用函数的Monad
实例将double
的返回值用作 {em}两个参数的(,)
函数,然后将increment
应用于结果元组中元素的 second 。
只需一次导入,您就可以简化中间部分:
import Control.Monad
doubleThenIncrement = fmap increment . join (,) . double
(对于函数,join
的实现实际上是join f = \x -> f x x
。我们使用(,)
的结果为double
调用join (,) . double
。\x -> (,) (double x) (double x)
是{ {1}}是(double x, double x)
。)
您可以定义一个以increment
和double
作为参数的函数来返回doubleThenIncrement
:
> let composePreservingOutputs f g = fmap f . join (,) . g
> composePreservingOutputs increment double $ 2
(4,5)
在所有可怕的无点荣耀中,它将是
-- Courtesy of pointfree.io
composePreservingOutputs = (. (join (,) .)) . (.) . fmap
(或者,你可以写
composePreservingOutputs f g x = (f x, g (f x))
但那里的乐趣在哪里?)
答案 1 :(得分:1)
在Haskell中提出你想要的通用解决方案是很棘手的,因为中间值需要是相同的类型。
注销中间值的模式是Writer
monad适合的模式。这里的限制是“写入”值必须实现Monoid
类型类。如果您只是将其用于调试,我怀疑您的情况可以正常,即您可以将值转换为String
,这是Monoid
。
如果你喜欢箭头,我会定义一个函数writeParam f x = (show x, f x)
你可以包装你的所有函数。返回元组Monoid a => ((,) a)
的函数实际上是一个简单的编写器monad实现,所以你可以用这样的常规monad组合来组合这些函数:
doubleThenIncrement :: (Num n, Show n) => n -> (String, n)
doubleThenIncrement = writeParam double >=> writeParam increment
顺便说一下,如果你喜欢箭头,你也可以将writeParam
定义为writeParam = (show &&&)
,或者如果你想更通用的话,还可以定义writeParam = (id &&&)
。虽然IMO不太清楚。