57

普通函数组合的类型

(.) :: (b -> c) -> (a -> b) -> a -> c

我认为这应该概括为以下类型:

(.) :: (c -> d) -> (a -> b -> c) -> a -> b -> d

一个具体的例子:计算差异平方。我们可以写diffsq a b = (a - b) ^ 2,但感觉我应该能够作曲(-)(^2)写出类似的东西diffsq = (^2) . (-)

我当然不能。我可以做的一件事是使用元组而不是两个参数来(-)转换它uncurry,但这是不一样的。

有可能做我想做的事吗?如果不是,我有什么误解让我认为它应该是可能的?


注意:这实际上已经在这里问过了,但没有给出答案(我怀疑必须存在)。

4

6 回答 6

53

我的首选实现是

fmap . fmap :: (Functor f, Functor f1) => (a -> b) -> f (f1 a) -> f (f1 b)

如果只是因为它很容易记住。

当分别实例化 f 和 f1 时(->) c(->) d您将获得类型

(a -> b) -> (c -> d -> a) -> c -> d -> b

这是类型

(.) . (.) ::  (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c

但是更容易说出fmap . fmap版本并且它可以推广到其他仿函数。

有时这是写成fmap fmap fmap的,但写成fmap . fmap它可以更容易地扩展以允许更多参数。

fmap . fmap . fmap 
:: (Functor f, Functor g, Functor h) => (a -> b) -> f (g (h a)) -> f (g (h b))

fmap . fmap . fmap . fmap 
:: (Functor f, Functor g, Functor h, Functor i) => (a -> b) -> f (g (h (i a))) -> f (g (h (i b))

等等

一般fmap用自己组成n次就可以用到fmap n级深!

并且由于函数形成 a Functor,这为n参数提供了管道。

有关详细信息,请参阅 Conal Elliott 的语义编辑器组合器

于 2011-04-28T17:08:35.090 回答
42

误解是您将类型函数a -> b -> c视为具有返回类型的两个参数的函数c,而实际上它是具有返回类型的一个参数的函数,b -> c因为函数类型关联到右侧(即它与a -> (b -> c).this使得无法使用标准函数组合运算符。

要了解原因,请尝试将(.)operator 类型的(y -> z) -> (x -> y) -> (x -> z)运算符应用于两个函数g :: c -> df :: a -> (b -> c). 这意味着我们必须yc和也与统一b -> c。这没有多大意义。怎么可能y同时c返回一个函数c?那必须是无限类型。所以这不起作用。

仅仅因为我们不能使用标准的组合运算符,它并不能阻止我们定义自己的。

 compose2 :: (c -> d) -> (a -> b -> c) -> a -> b -> d
 compose2 g f x y = g (f x y)

 diffsq = (^2) `compose2` (-)

通常在这种情况下最好避免使用无点样式,而直接使用

 diffsq a b = (a-b)^2
于 2011-04-28T15:38:51.587 回答
24

我不知道执行此操作的标准库函数,但完成它的无点模式是组合组合函数:

(.) . (.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c
于 2011-04-28T15:56:34.103 回答
8

我打算在评论中写这个,但它有点长,它来自mightybyte和hammar。

我建议我们围绕诸如.*forcompose2.**for之类的运算符进行标准化compose3。使用mightybyte的定义:

(.*) :: (c -> d) -> (a -> b -> c) -> (a -> b -> d)
(.*) = (.) . (.)

(.**) :: (d -> e) -> (a -> b -> c -> d) -> (a -> b -> c -> e)
(.**) = (.) . (.*)

diffsq :: (Num a) => a -> a -> a
diffsq = (^2) .* (-)

modminus :: (Integral a) => a -> a -> a -> a
modminus n = (`mod` n) .* (-)

diffsqmod :: (Integral a) => a -> a -> a -> a
diffsqmod = (^2) .** modminus

是的,modminus并且diffsqmod是非常随机且毫无价值的功能,但它们很快并且表明了重点。请注意,通过在另一个 compose 函数中组合来定义下一个级别是多么容易(类似于fmapEdward 提到的链接 s)。

(.***) = (.) . (.**)

实际上,从compose12上往下写函数名比写操作符要短

f .*********** g
f `compose12` g

尽管计算星号很累,所以我们可能希望在 4 或 5 处停止约定。


[编辑] 另一个随机的想法,我们可以使用.:compose2、.:.compose3、.::compose4、.::.compose5、.:::compose6,让点的数量(在最初的点之后)直观地标记要向下钻取的参数数量。不过我觉得我更喜欢星星。

于 2011-04-28T17:41:35.133 回答
6

正如Max在评论中指出的那样:

diffsq = ((^ 2) .) . (-)

您可以认为是f . g将一个参数应用于g,然后将结果传递给f(f .) . g将两个参数应用于g,然后将结果传递给f((f .) .) . g将三个参数应用于g,依此类推。

\f g -> (f .) . g :: (c -> d) -> (a -> b -> c) -> a -> b -> d

如果我们将组合运算符与某些功能f :: c -> df左侧的部分应用程序)分开,我们会得到:

(f .) :: (b -> c) -> b -> d

所以我们有这个新函数,它需要一个来自 的函数b -> c,但是我们的gis a -> b -> c,或者等效地,a -> (b -> c)。我们需要先申请一个a,然后才能得到我们需要的东西。好吧,让我们再次迭代:

((f .) .) :: (a -> b -> c) -> a -> b -> d
于 2016-03-23T03:27:40.563 回答
3

我认为这是实现您想要的一种优雅的方式。类型类提供了一种将Functor函数“推送”到容器中的方法,因此您可以使用fmap. 您可以将函数a -> b视为bs 的容器,其中每个元素由 的元素索引a。所以很自然地做这个实例:

instance Functor ((->) a) where
  fmap f g = f . g

(我认为你可以import通过一个合适的库来获得它,但我不记得是哪个。)

f现在with的通常组成g很简单fmap

o1 :: (c -> d) -> (b -> c) -> (b -> d)
f `o1` g = fmap f g

type 的函数是 typea -> b -> c元素容器的容器c。所以我们只需要将函数f向下推两次。干得好:

o2 :: (c -> d) -> (a -> (b -> c)) -> a -> (b -> d)
f `o2` g = fmap (fmap f) g

在实践中,您可能会发现您不需要o1o2,只需fmap. 如果你能找到我忘记了位置的库,你可能会发现你可以直接使用fmap而无需编写任何额外的代码。

于 2011-04-28T17:18:06.930 回答