我想组合加法运算符(+)
来创建这种类型的函数:
Num a => a -> a -> a -> a
就像,相当于:
(\a b c -> a + b + c)
但不必诉诸lambdas。
我已经尝试了
((+) . (+))
我希望它可以工作,但令人惊讶的是没有。
答案 0 :(得分:11)
http://pointfree.io将\a b c -> a + b + c
的无点版本视为((+) .) . (+)
。
非正式地,组合仅对“一阶函数”“直观地”工作,它既不将函数作为参数,也不将函数作为值返回。 (+)
是一个高阶函数;它采用类型Num a => a
的值,并返回类型为Num a => a -> a
的函数。当您尝试以天真的方式撰写高阶函数时,结果并非您所期望的结果:
:t (+) . (+)
(+) . (+) :: (Num a, Num (a -> a)) => a -> (a -> a) -> a -> a
考虑两个函数的定义:
(+) :: Num z => z -> z -> z
(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \x -> f (g x)
然后
(+) . (+) == (.) (+) (+)
== \x -> (+) ((+) x)
由于currying,你结束了传递函数而不是数字作为第一个(+)
的第一个参数。
那么我们如何从h a b c = a + b + c
转到h = ((+) .) . (+)
?首先将中缀表达式重写为前缀表达式,使用(+)
左关联的事实。
\a b c -> a + b + c
== \a b c -> ((+) a b ) + c
== \a b c -> (+) ((+) a b) c
接下来,我们交替应用eta转换来消除参数和组合,以将参数移动到要消除的位置。我试图非常明确地确定用于组合应用的函数。
== \a b -> (+) ((+) a b) -- eta conversion to eliminate c
== \a b -> (+) (((+) a) b) -- parentheses justified by currying
-- f g -- f = (+), g = ((+) a)
-- \a b -> f ( g b)
-- \a b -> (f . g) b -- definition of (.)
== \a b -> ((+) . ((+) a)) b
== \a -> (+) . ((+) a) -- eta conversion to eliminate b
== \a -> (.) (+) ((+) a) -- prefix notation
== \a -> ((.) (+)) ((+) a) -- parentheses justified by currying
== \a -> ((+) . )((+) a) -- back to a section of (.)
-- f g -- f = ((+) .), g = (+)
-- \a -> f (g a)
-- \a -> ( f . g) a -- definition of (.)
== \a -> (((+) .) . (+)) a
== ((+) .) . (+) -- eta conversion to eliminate a
答案 1 :(得分:8)
你需要这个奇怪的运算符(.).(.)
,它有时定义为.:
(想想3个点......)
在ghci
Prelude> let (.:) = (.).(.)
Prelude> let f = (+) .: (+)
Prelude> f 1 2 3
> 6
请注意,此运算符也可以定义为<$$> = fmap . fmap
。
答案 2 :(得分:6)
虽然这会引入一些噪音,但您可以使用uncurry :: (a -> b -> c) -> (a,b) -> c
和curry :: ((a,b) -> c) -> a -> b -> c
将第二个加号的参数临时存储在一个元组中:
curry $ (+) . uncurry (+) :: Num a => a -> a -> a -> a
或者可能更具语义可读性:
curry ((+) . uncurry (+)) :: Num a => a -> a -> a -> a
uncurry
因此需要一个函数(此处为(+)
)并将其转换为函数:uncurry (+) :: Num a => (a,a) -> a
。因此,您已将(+)
转换为带有元组的函数。
现在,我们可以使用(.)
制作包含第一个(+)
的作品:
(+) . uncurry (+) :: Num a => (a,a) -> (a -> a)
所以现在我们有一个函数接受一个参数(元组(a,a)
)并生成一个函数,它接受a
(第一个(+)
的第二个操作数)并计算和。问题当然是我们要摆脱元组。我们可以通过将函数传递给curry
来实现。这会将元组函数((a,a) -> (a -> a)
)转换为单独使用参数的函数(a -> (a -> (a -> a))
)。
答案 3 :(得分:4)
注意函数组合运算符的签名:
(.) :: (b -> c) -> (a -> b) -> a -> c
^ ^ ^
Functions
它需要2个函数,每个函数取1个参数,并返回一个函数,该函数接受与第二个函数相同类型的参数,并返回与第一个函数相同的类型。
由于+
需要2个参数,因此您尝试撰写两个+
的功能并不起作用,因此如果没有一些hackish /创造性的解决方法,这是不可能的。
此时,我说在不适合问题的情况下强迫合成只会让你的生活变得更加困难。
如果您想总结多个数字,可以编写如下函数:
sum :: [Int] -> Int
sum nums = foldl (+) 0 nums
或者,由于nums
出现在定义的后面,因此可以将其完全删除,从而产生一个无点的&#34;形式:
sum :: [Int] -> Int
sum = foldl (+) 0
它会在数字列表中缩小/折叠+
。如果您尚未使用折叠, 现在会查看它们 。它们是在Haskell中实现循环的主要方法之一。它本质上是一种隐含的递归&#34;当你处理列表或其他任何可迭代的时候。
定义上述功能后,您可以使用它:
sum [1, 2 3, 4, 5]