How to understand the function "(.) (.)" In Haskell

I start in Haskell, and I come across a function (.)(.) , I use :t to get my type in GHCi:

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

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

+7
types haskell
source share
3 answers

This is a partial application of the composition operator to the composition itself. In the general case, we know that if we apply (.) To some function f :: x -> y , then

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

due to the way the types are arranged:

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

We discard the first argument and replace the remaining occurrences of b and c corresponding types of this argument.

Here f again is simply (.) , Which means that we identify x ~ (b -> c) and y ~ (a -> b) -> a -> c . Re-aligning types

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

Since a occurs above and below, we need to select a new variable name for a below; GHC chose a1 :

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

Putting the two together gives the type that you see in GHCi.

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

Anatomy jokes aside, what is (.)(.) ?

Say you have a function f :: a -> b , but you need a function g :: a -> c , that is, you want f , but with a different return type. The only thing you can do is find the helper function h :: b -> c , which converts the return value for you. Your g function is just a composition of h and f :

 g = h . f 

However, you may have a more general function h' :: t -> b -> c , which can turn values ​​of type b into values ​​of type c several ways, depending on the value of some argument x :: t . Then you can get many different g depending on this argument.

 g = (h' x) . f 

Now, given h' , x and f , we can return our g , so we write a function that does this: a function that "contributes" to the return value of f from a value of type b by a value of type c specified by the function h' and some value x :

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

You can mechanically convert any function to non-contact form; I am not familiar with the details, but using PointFree.io produces

 promote = ((.) .) 

which is only a partial application (.) (.) written as a section, i.e.:

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

So, our boobs operator is just a generalized pre-layout operator.

+2
source share

The function (.) (b -> c) -> (a -> b) -> (a -> c) type (b -> c) -> (a -> b) -> (a -> c) , that is, two functions are given: one from a to b and one from b to c , it glues them together together to form one a to c .

Write the type (.) Again, but using different letters to distinguish them: (y -> z) -> (x -> y) -> (x -> z) . Say the abc version is the first (.) In (.)(.) , And the xyz version is second. We pass the second first argument to the first. Remember that the first argument of the first is of type (b -> c) , so we need to match it with the type of the second function.

You may notice that there is a mismatch here: (b -> c) is a function that takes one argument, but (.) Takes two. But in Haskell, all functions have a curried value, which means that a function that takes two arguments is really the same as a function that takes one argument and returns another function that takes one argument (the second of the two original ones), and only this function then returns the actual result.

Or, in other words, the arrow type constructor is linked from right to left, and we can enclose in parentheses to make it clearer: for our purposes, the type of the second (.) Is better written as (y -> z) -> ((x -> y) -> (x -> z)) . Compare this with (b -> c) , and it is clear that this means that b = (y -> z) and c = ((x -> y) -> (x -> z)) .

The first argument given is the result of the rest of the first (.) With the replacement of type variables with our substitutions - therefore the type (.)(.) (a -> (y -> z)) -> (a -> ((x -> y) -> (x -> z))) .

Now we can drop all the parentheses to the right of the 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) that GHCi has given you.

And this type and function means: "given the binary function b , which takes a and a y and returns a z , and also gives a va value of type a , and given the unary function u , which takes x and returns a y , and finally gives a vx value of type x , give me z , which is obtained from calculating b va (u vx) .

You probably won't need this. The only reason the feature is interesting is because it looks like boobs.

+2
source share
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ ↓ ↓ ↓ β”‚ ↓ β”‚ β”‚ β”‚ (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? The function of one argument. Its type must be b->c , because we only 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 like b .

Then we can take a function of type b->c computed in the first step and apply it to this thing of type b computed in the second step. This gives us something like c , the result of the result of the whole construction (.)(.) That we need.

Notice how all this can be detected by looking only at the type. No need to know how the function was originally implemented at all.

0
source share

All Articles