普通功能组成属于
类型(.) :: (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,但答案(我怀疑必须存在)没有给出。
答案 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)
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
的函数,但我们的g
是a -> 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
在实践中,您可能会发现自己不需要o1
或o2
,只需要fmap
。如果你能找到我忘记了位置的图书馆,你可能会发现你可以只使用fmap
而无需令状
任何额外的代码。