函数组合 (.) 运算符如何处理多个组合?

Aka*_*ari 2 haskell function-composition

我知道点 (.) 运算符被定义为采用三个参数(两个函数和一个类型为“a”的值),并且通过将参数应用于右侧的函数和左侧的函数来工作应用于左侧应用程序的输出,如下所示:

-- Associativity: infixr 9
(.) :: (b -> c) -> (a -> b) -> a -> c
(f . g) x = f(g x)
-- Or in another format
f . g = \x -> f(g x)
Run Code Online (Sandbox Code Playgroud)

现在,这就是在函数组合中使用点运算符的多个应用程序令人困惑的地方,请考虑这一点:

对于像 (+) 这样的中缀运算符,它采用“Num”类中类型的两个参数。

(+) :: Num a => a -> a -> a

-- An application would look like this:
> 4 + 3
7

-- With more applications composed with (+)
> 4 + 3 + 2
9

-- Which is fine, since the last application
-- still takes two arguments of the legitimate types,
-- which is a value of type within "Num" class, as follows
> (4 + 3) + 2
-- 7 + 2
9
Run Code Online (Sandbox Code Playgroud)

但使用点运算符,如下所示。最后一个带有点运算符的函数组合应该采用应用程序“reverse . take 3 $map (*2) [1..]”的输出,即前一个函数组合的“[6,4,2]” 。

如果这就是我所说的,点运算符采用一个值而不是函数和右侧的值,那么它应该如何工作?

> replicate 4 . reverse . take 3 $ map (*2) [1..]
[[6,4,2],[6,4,2],[6,4,2],[6,4,2]]
Run Code Online (Sandbox Code Playgroud)

中间步骤不应该像下面这样吗?

-- And it's not going to work, since the dot operator
-- is defined to take three arguments of two functions
-- "(b -> c)" and "(a -> b)"
-- and the value of type "a"
replicate 4 . [6,4,2]
Run Code Online (Sandbox Code Playgroud)

由于点运算符的应用程序采用三个参数,然后返回类型“a”的值的结果。

因此左侧的“reverse . take 3 $map (*2) [1..]”部分应评估为“[6,4,2]”

为什么不是呢?

lsm*_*mor 6

你会对柯里化感到困惑(稍后会详细介绍)。您可以认为(.)采用两个参数(就像这样(+)做一样)。

-- Associativity: infixr 9
--                             |- this parens. aren't necessary, but It helps to make them explicit
(.) :: (b -> c) -> (a -> b) -> (a -> c)
--     |- input 1  |- input 2  |- result function 
Run Code Online (Sandbox Code Playgroud)

现在,因为(.)它是正确的关联,所以下面的两行代码是等效的

replicate 4 . reverse . take 3   
replicate 4 . (reverse . take 3) -- by right assoc. property
Run Code Online (Sandbox Code Playgroud)

为了使这一点更明确让我重写上面的代码

replicate 4 . reverse . take 3   
replicate 4 . (reverse . take 3) -- by right assoc. property
Run Code Online (Sandbox Code Playgroud)

现在,我们在这里应用的技术称为柯里化。这可以概括为“一个带有两个参数的函数可以重写为一个带有一个参数并返回另一个带有一个参数的函数”。

为了理解一些Python代码

-- original
doSomething = replicate 4 . reverse . take 3 $ map (*2) [1..]

-- original with explicit parenthesis
doSomething = ( replicate 4 . (reverse . take 3) ) (map (*2) [1..])
              |               |- right assoc  -| | |              |
              |                                  | |              |
              |- these four are the result of deleting $ symbol  -|

-- factor out each parenthesis. Types are monomorphise for clarity.
doSomething = (replicateFour . takeThreeAndReverse) evenNumbers 
  where replicateFour       = replicate 4      -- :: [Int] -> [[Int]]  
        takeThreeAndReverse = reverse . take 3 -- :: [Int] -> [Int]
        evenNumbers         = map (*2) [1..]   -- :: [Int]
Run Code Online (Sandbox Code Playgroud)

现在,所有 haskell 函数都被柯里化了,这实际上对函数式语言来说很有意义,因为如果简化了很多组合并使语言在大多数情况下更符合人体工程学(尽管,这是一种观点,其他人可能有不同的看法)

柯里化一开始可能会让人头疼,但最终它会变得很自然。克服它的最好方法是做大量的练习并尝试使用大量的部分应用