简体   繁体   English

如何理解Haskell中的函数“(。)(。)”

[英]How to understand the function “(.)(.)” in Haskell

I am a beginner in Haskell and I come across the function (.)(.) , I use :t to get its type in GHCi: 我是Haskell的初学者,我遇到函数(.)(.) ,我使用:t来获取GHCi中的类型:

:t (.)(.)
(.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c

How to understand the type (.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c here? 如何理解类型(.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c这里? I am very confused. 我很困扰。

The function (.) has the type (b -> c) -> (a -> b) -> (a -> c) , ie given two functions, one from a to b and one from b to c , it sticks them together to form a single a to c function. 函数(.)具有类型(b -> c) -> (a -> b) -> (a -> c) ,即给定两个函数,一个从ab ,一个从bc ,它坚持它们一起形成单个ac功能。

Let's write the type of (.) again, but using different letters to distinguish them: (y -> z) -> (x -> y) -> (x -> z) . 让我们再次写出(.)的类型,但使用不同的字母来区分它们: (y -> z) -> (x -> y) -> (x -> z) Let's say the abc version is the first (.) in (.)(.) , and the xyz version is the second. 假设abc版本是(.)中的第一个(.)(.) ,而xyz版本是第二个。 We pass the second as the first argument to the first. 我们将第二个作为第一个参数传递给第一个参数。 Remember that the first argument to the first has the type (b -> c) , so we need to match that against the type of the second function. 请记住,第一个参数的第一个参数有类型(b -> c) ,所以我们需要将它与第二个函数的类型相匹配。

You may notice that there's a mismatch here: (b -> c) is a function that takes one argument, but (.) takes two. 您可能会注意到这里存在不匹配: (b -> c)是一个接受一个参数的函数,但(.)需要两个参数。 But in Haskell, all functions are curried, which means that a function taking two arguments is really the same thing as a function that takes one argument and returns another function that takes one argument (the second of the original two), and only that function then returns the real result. 但是在Haskell中,所有函数都是curry,这意味着一个带两个参数的函数与一个带有一个参数并返回另一个带有一个参数的函数(原始二个中的第二个)的函数实际上是相同的,并且只有那个函数然后返回真实结果。

Or in other words, the arrow type constructor binds right to left, and we can put in parentheses to make it clearer: for our purposes, the type of the second (.) is better written as (y -> z) -> ((x -> y) -> (x -> z)) . 换句话说,箭头类型构造函数从右到左绑定,我们可以放入括号使其更清晰:为了我们的目的,第二个(.)类型更好地写为(y -> z) -> ((x -> y) -> (x -> z)) Match this against (b -> c) and it is clear that this means that b = (y -> z) and c = ((x -> y) -> (x -> z)) . 将其与(b -> c)匹配,很明显这意味着b = (y -> z)c = ((x -> y) -> (x -> z))

The first argument being given, the result is the remainder of the type of the first (.) with the type variables replaced by our substitutions - the type of (.)(.) is therefore (a -> (y -> z)) -> (a -> ((x -> y) -> (x -> z))) . 给出的第一个参数,结果是第一个(.)类型的其余部分,其中类型变量被我们的替换替换 - 因此(.)(.)的类型是(a -> (y -> z)) -> (a -> ((x -> y) -> (x -> z)))

Now we can drop all parentheses that are on the right of an arrow to simplify this expression and get (a -> y -> z) -> a -> (x -> y) -> x -> z . 现在我们可以删除箭头右边的所有括号来简化这个表达式并得到(a -> y -> z) -> a -> (x -> y) -> x -> z It is easy to see that this is exactly (modulo renaming) what GHCi gave you. 很容易看出GHCi给你的确切(模数重命名)。

And this type and function means, "given a binary function b that takes an a and a y and returns a z , and given also a value va of type a , and given an unary function u that takes an x and returns a y , and given finally a value vx of type x , give me the z that results from the computation b va (u vx) . 这种类型和函数意味着,“给定一个二进制函数b ,它取一个a和一个y并返回一个z ,并给出一个类型a a的值va ,并给出一个带有x并返回y的一元函数u ,最后给出一个x类型的值vx ,给我从计算b va (u vx)得到的z

You probably won't need it. 你可能不需要它。 The only reason the function is interesting is that it looks like boobs. 功能很有趣的唯一原因是它看起来像胸部。

This is a partial application of the composition operator to the composition operator itself. 这是该组合物操作的局部应用组合物操作者本身。 In general, we know that if we apply (.) to some function f :: x -> y , then 一般来说,我们知道如果我们将(.)应用于某个函数f :: x -> y ,那么

>>> :t (.) f
(.) f :: (a -> x) -> a -> y

because of how the types line up: 因为类型如何排列:

(b -> c) -> (a -> b) -> a -> c
 x -> y
--------------------------------
            (a -> x) -> a -> y

We drop the first argument, and replace remaining occurrences of b and c with the corresponding types of the given argument. 我们删除第一个参数,并用给定参数的相应类型替换剩余的bc出现。

Here, f is just (.) again, meaning we identify x ~ (b -> c) and y ~ (a -> b) -> a -> c . 这里, f再次只是(.) ,意味着我们识别x ~ (b -> c)y ~ (a -> b) -> a -> c Lining up the types again 再次排列类型

(a ->   x   )  -> a ->              y
      b -> c               (a -> b) -> a -> c

Since a occurs on the top and the bottom, we need to pick a new variable name for a on the bottom; 由于a在顶部和底部出现时,我们需要选择一个新的变量名称为a底部; GHC chose a1 : GHC选择了a1

(a ->   x   )  -> a ->               y
      b -> c               (a1 -> b) -> a1 -> c

Putting the two together yields the type you see in GHCi. 将两者放在一起会产生您在GHCi中看到的类型。

(a -> b -> c) -> a -> (a1 -> b) -> a1 -> c

Anatomy jokes aside, what is (.)(.) ? 除了解剖学笑话,什么 (.)(.)

Let's say you have a function f :: a -> b , but you want a function g :: a -> c , that is, you want f but with a different return type. 假设你有一个函数f :: a -> b ,但是你想要一个函数g :: a -> c ,也就是说,你想要f但具有不同的返回类型。 The only thing you can do is find a helper function h :: b -> c that will convert the return value for you. 你唯一能做的就是找一个辅助函数h :: b -> c ,它将为你转换返回值。 Your function g is then simply the composition of h and f : 你的函数g就是hf的组合:

g = h . f

You might, however, have a more general function h' :: t -> b -> c , which can turn values of type b into values of type c in multiple ways, depending on the value of some argument x :: t . 但是,您可能有一个更通用的函数h' :: t -> b -> c ,它可以多种方式将b类型的值转换为c类型的值,具体取决于某些参数x :: t的值。 Then you could get lots of different g s depending on that argument. 然后根据该参数可以获得许多不同的g s。

g = (h' x) . f

Now, given h' , x , and f , we can return our g , so let's write a function that does that: a function that "promotes" the return value of f from a value of type b to a value of type c , given a function h' and some value x : 现在,给定h'xf ,我们可以返回我们的g ,所以让我们编写一个函数来执行该操作:将f的返回值从类型b的值“提升”到类型c的值的函数,给定函数h'和某个值x

promote h' x f = (h' x) . f

You can mechanically convert any function to point-free form; 您可以将任何功能机械转换为无点形式; I'm not familiar with the details, but using PointFree.io produces 我不熟悉细节,但使用PointFree.io生成

promote = ((.) .)

which is just the partial application (.) (.) written as a section, that is: 这只是部分应用程序(.) (.)写成一个部分,即:

((.) (.)) h' x f == (h' x) . f

So, our "boobs" operator is just a generalized pre-composition operator. 因此,我们的“胸部”操作员只是一个广义的预组合算子。

           ┌──────────────────────────────────────────────────────────────────────────┐ 
           │                                                                          │
      ┌─────────────────────────────────────────────────┐                             │
      │    │                                            │                             │
 ┌─────────────────────────┐                      ┌──────────────────────┐            │
 │    │    │               │                      │     │                │            │
 ↓    ↓    ↓               │                      ↓     │                │            │ 
(a -> b -> c)      ->      a          ->         (a1 -> b)        ->     a1     ->    c
 ───────────              ───                     ───────                ──        
      ↓                    ↓                         ↓                   ↓
     (f)                  (x)                       (g)                 (y)
      ↓                    ↓                         ↓                   ↓
a function        a thing that works      a function of one        a thing that
of two arguments  as the first argument   argument that            works as the
that returns      of f                    returns a thing          argument of g
the same type                             suitable as the second
(.)(.) returns                            argument of f

Now how can we combine these four things? 现在我们如何将这四件事结合起来?

First we can take f and apply it to x . 首先,我们可以将f应用于x What does this give us? 这给了我们什么? A function of one argument. 一个参数的函数。 Its type should be b->c , because we have just applied a function of type a->b->c to an argument of type a . 它的类型应该是b->c ,因为我们刚才应用类型的功能a->b->c到类型的参数a

Then we can take the second g and apply it to y . 然后我们可以取第二个g并将其应用于y This gives us something of type b . 这给了我们类型b东西。

Then we can take a function of type b->c computed at the first step, and apply it to that thing of type b computed at the second step. 然后我们可以采取类型的函数b->c在第一步骤中计算,并将其应用到类型的那个东西b在第二步骤中计算。 This gives us something of type c , the result type of the entire (.)(.) construction, which is just what we need. 这给了我们类型c东西,整个(.)(.)构造的结果类型,这正是我们需要的。

Note how all of this can be discovered by looking at just the type. 注意通过查看类型可以发现所有这些。 No need to know how the function was originally implemented at all. 无需知道该函数最初是如何实现的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM