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:

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

How to understand the type (.)(.) :: (a -> b -> c) -> a -> (a1 -> b) -> a1 -> c here? I am very confused.


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

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

Here, f is just (.) again, meaning we identify x ~ (b -> c) and 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; GHC chose a1 :

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

Putting the two together yields the type you see in 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. The only thing you can do is find a helper function h :: b -> c that will convert the return value for you. Your function g is then simply the composition of h and f :

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 . Then you could get lots of different g s depending on that argument.

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 :

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

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.


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.

Let's write the type of (.) again, but using different letters to distinguish them: (y -> z) -> (x -> y) -> (x -> z) . Let's say the abc version is the first (.) in (.)(.) , and the xyz version is the second. 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.

You may notice that there's a mismatch here: (b -> c) is a function that takes one argument, but (.) takes two. 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.

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)) . Match this against (b -> c) and it is clear that this means that b = (y -> z) and 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))) .

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 . It is easy to see that this is exactly (modulo renaming) what GHCi gave you.

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

You probably won't need it. The only reason the function is interesting is that it looks like boobs.


           ┌──────────────────────────────────────────────────────────────────────────┐ 
           │                                                                          │
      ┌─────────────────────────────────────────────────┐                             │
      │    │                                            │                             │
 ┌─────────────────────────┐                      ┌──────────────────────┐            │
 │    │    │               │                      │     │                │            │
 ↓    ↓    ↓               │                      ↓     │                │            │ 
(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 . 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 .

Then we can take the second g and apply it to y . This gives us something of type 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. This gives us something of type c , the result type of the entire (.)(.) construction, which is just what we need.

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.

链接地址: http://www.djcxy.com/p/93012.html

上一篇: 使用Shopify将产品添加到购物车中

下一篇: 如何理解Haskell中的函数“(。)(。)”