haskell 函数复合(.)操作符如何处理多个复合?

xzlaal3s  于 2024-01-08  发布在  其他
关注(0)|答案(2)|浏览(108)

我知道点(.)运算符被定义为接受三个参数(两个函数和一个类型为“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)

字符串
现在,这就是在函数复合中使用点运算符的多个应用程序的困惑所在,考虑一下:
使用中缀操作符(如(+)),它接受“”类中类型的两个参数。

(+) :: 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


但是使用点运算符,如下所示。最后一个使用点运算符的函数组合应该采用应用程序的输出“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]]


中间的舞步不应该是跟步吗?

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


因为点运算符的应用程序接受三个参数,然后返回类型为“a”的值的结果。
所以左边的“reverse . take 3 $ map(*2)[1..]”部分应该被计算为“[6,4,2]”
为什么不是呢?

kxeu7u2r

kxeu7u2r1#

你会被currification弄糊涂(后面会有更多的介绍),你可以把(.)看作是接受两个参数(就像(+)一样)。

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

字符串
现在,因为(.)是正确的,所以下面的两行代码是等效的

replicate 4 . reverse . take 3   
replicate 4 . (reverse . take 3) -- by right assoc. property


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

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


现在,我们在这里应用的技术被称为currying。可以总结为“一个接受两个参数的函数可以重写为一个接受一个参数的函数,并返回另一个接受一个参数的函数。”
一些python代码,以便于理解

# The following two are equivalent functions. 
def add(x, y):
  return x + y

def add_curry(x):
  def inner(y):
    return x + y
  return inner # we return a function not a value

add(3, 4) == add(3)(4) # notice double function call in RHS


现在,所有的haskell函数都是curried的,这实际上在函数式语言上很有意义,因为它简化了很多组合,并使语言在大多数情况下更符合人体工程学(当然,这是一个观点,其他人可能会有不同的看法)
咖喱在开始的时候可能会让人头疼,但最终它会变得很自然。克服它的最好方法是做大量的练习,并尝试使用大量的局部应用

v2g6jxz6

v2g6jxz62#

简而言之,在多功能组合物的应用中,(t.z.f. g)”,函数composition的每个单独的composition将首先从右到左,通过点运算符的“infixr 9”的优先级进行评估,然后应用于参数“x”,因为组合和给定的参数由美元运算符分隔,美元运算符定义为优先级为“infixr 0”。

The composition: (t . z . f . g)

Each function's type:
t :: (d -> e)
z :: (c -> d)
f :: (b -> c)
g :: (a -> b)

Actual type: 
(t . z . f . g) :: a -> e

字符串
现在,由于currying,点运算符被定义为接受两个函数和一个值的三个参数,即“(b -> c)",“(a -> b)”和“a”,如果点运算符只接受这两个函数,那么它将返回一个函数“(a -> c)”作为下一个点运算符的右操作数,依此类推。

Type in process for each composition with the dot operator:
(d -> e) . (c -> d) . (b -> c) . (a -> b) $ a
((d -> e) . (c -> d) . (b -> c) . (a -> b)) a
((d -> e) . (c -> d) . (a -> c)) a
((d -> e) . (a -> d)) a
(a -> e) a


在一个真实的案例中:

> :t (replicate 3 . reverse . take 3 . map (*3))
(replicate 3 . reverse . take 3 . map (*3))
  :: Num a => [a] -> [[a]]
> replicate 3 . reverse . take 3 . map (*3) $ [1..]
[[9,6,3],[9,6,3],[9,6,3]]


它像预期的那样工作:)
感谢@lsmor详细和鼓舞人心的回答。这篇文章是我的理解方式,不是关于哪个更好。非常感谢!

相关问题