“加”运算符加入一个接受3个参数的函数?

时间:2017-02-07 16:30:50

标签: function haskell composition

我想组合加法运算符(+)来创建这种类型的函数:

Num a => a -> a -> a -> a

就像,相当于:

(\a b c -> a + b + c)

但不必诉诸lambdas。

我已经尝试了

((+) . (+))

我希望它可以工作,但令人惊讶的是没有。

4 个答案:

答案 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) -> ccurry :: ((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]