类型(c→d)→(a→b→c)→(a→b→d)的Haskell函数组合算子

时间:2011-04-28 15:29:19

标签: haskell function-composition pointfree

普通功能组成属于

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

我认为这应该概括为类似:

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

一个具体的例子:计算差分平方。我们可以写diffsq a b = (a - b) ^ 2,但感觉就像我应该能够撰写(-)(^2)来编写类似diffsq = (^2) . (-)的内容。

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

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


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

6 个答案:

答案 0 :(得分:43)

我首选的实现是

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))

一般情况下,由{em> n 时间组成的fmap可以用于fmap n 级别!

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

有关更多信息,请参阅Conal Elliott的Semantic Editor Combinators

答案 1 :(得分:28)

误解是你认为类型为a -> b -> c的函数是两个带有返回类型c的参数的函数,而它实际上是一个返回类型为{{1}的参数的函数因为函数类型与右边相关(即它与b -> c相同。这使得无法使用标准函数组合运算符。

要了解原因,请尝试将a -> (b -> c)运算符(.)运算符应用于两个函数(y -> z) -> (x -> y) -> (x -> z)g :: c -> d。这意味着我们必须将f :: a -> (b -> c)y以及c统一起来。这没有多大意义。 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` (-)

答案 2 :(得分:16)

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

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

答案 3 :(得分:7)

我打算在评论中写这个,但它有点长,它来自强大的和哈马尔。

我建议我们围绕.*的{​​{1}}和compose2 .**等运算符进行标准化。使用mightybyte的定义:

compose3

是的,(.*) :: (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是非常随意且毫无价值的功能,但它们很快并且显示了重点。请注意通过在另一个撰写函数中编写来定义下一个级别是多么容易(类似于Edward提到的链接diffsqmod)。

fmap

从实际角度来看,从(.***) = (.) . (.**) 开始,编写函数名称而不是运算符会更短

compose12

虽然计算星号很累,但我们可能想要在4或5时停止惯例。


[edit]另一个随意的想法,我们可以将f .*********** g f `compose12` g 用于compose2,将.:用于compose3,将.:.用于compose4,将.::用于compose5,.::.对于compose6,让点数(在初始点之后)可视地标记要向下钻取的参数数量。我觉得我更喜欢明星了。

答案 4 :(得分:4)

Max中指出comment

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 -> d(左边是f的部分应用程序)对组合运算符进行左侧分割,我们得到:

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

所以我们有这个新函数需要b -> c的函数,但我们的ga -> b -> c,或者等价a -> (b -> c)。我们需要先应用a才能获得所需内容。好吧,让我们再次迭代:

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

答案 5 :(得分:3)

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

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

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

现在f g的{​​{1}}的通常构成通常是fmap

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

类型a -> b -> c的函数是c类型元素的容器的容器。所以我们只需要将函数f按下两次。你走了:

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

在实践中,您可能会发现自己不需要o1o2,只需要fmap。如果你能找到我忘记了位置的图书馆,你可能会发现你可以只使用fmap而无需令状 任何额外的代码。