X <*> y <$> z in Haskell

I am trying to understand the source code of Haskell, and I have met this structure several times:

 x <*> y <$> z 

eg.

 (+) <*> (+1) <$> a 

Can someone explain this structure to me? I understand that it translates to fmap a (+ a + 1) , but I cannot establish a connection

+5
source share
2 answers

Let's start with:

 x <*> y <$> z 

Adding parentheses:

 (x <*> y) <$> z 

Given that (<$>) :: Functor f => (a -> b) -> fa -> fb , we have:

 x <*> y :: a -> b z :: Functor f => fa 

Given that (<*>) :: Applicative g => g (c -> d) -> gc -> gd , we have:

 x :: Applicative g => g (c -> d) y :: Applicative g => gc x <*> y :: Applicative g => gd 

Combining the last few results, we get:

 gd ~ a -> b g ~ (->) a d ~ b x :: a -> c -> b y :: a -> c x <*> y :: a -> b 

In this way:

 (\xyz -> x <*> y <$> z) :: Functor f => (a -> c -> b) -> (a -> c) -> fa -> fb 

Now, knowing that (<*>) from the function instance is used, we can also substitute its definition:

 x <*> y <$> z (\r -> xr (yr)) <$> z 

In your example, x = (+) , y = (+1) and z = a , so we get ...

 (\r -> r + (r + 1)) <$> a 

... which adds each value in a to itself plus one:

 GHCi> (+) <*> (+1) <$> [0..3] [1,3,5,7] GHCi> ((+) <*> (+1) <$> (*5)) 2 21 
+4
source

So, in x <*> y <$> z , i.e. fmap (x<*>y) z , you use the function x<*>y by the value of the functor z . <*> actually knows nothing about fmapping - two operators work on completely separate functors! This is the first important thing that is being implemented here.

Further, if the result x<*>y is a function, then the Applicative <*> instance is actually a functor of the function. I would like people to stop using it like that because it really is one of the most confusing examples and not the most beautiful abstraction to choose from.

Specifically, f<*>g is just a smart way to compose the functions f and g , as well as directly input the initial input to f . It works like this:

 (<*>) :: (f ~ (x->)) => f (a -> b) -> fa -> fb 

i.e.

 (<*>) :: (x ->(a -> b)) -> (x -> a) -> (x -> b) ≑ (x -> a -> b) -> (x -> a) -> x -> b (f <*> g) x = fx $ gx 

In terms of data flow, this operation:

 ────┬─────▢ f ──▢ β”‚ β”‚ └─▢ g β”€β”€β”˜ 

I would rather express this with arrow combinators :

  β”Œβ”€β”€β”€id──┐ ────&&& uncurry f ──▢ └─▢ g β”€β”€β”˜ 

so f<*>g ≑ id &&& g >>> uncurry f . Of course, this is nowhere compact, actually more verbose than the explicit version of lambda \x -> fx $ gx , which, frankly, would probably be better. However, the arrow version is the most common version of the three, and perhaps best shows what is happening. The main reason why this is so much is because currying doesn't work here; we could define an operator

 (≻>>) :: (x->(a,b)) -> (a->b->c) -> x -> c g≻>>h = uncurry h . g 

and then

  x <*> y <$> z ≑ fmap (id &&& y ≻>> x) z ≑ fmap (\ΞΎ -> x ΞΎ $ y ΞΎ) z 

For example, we have

  (+) <*> (+1) <$> a ≑ fmap (id &&& (+1) ≻>> (+)) z ≑ fmap (\x -> 1 + x+1) z ≑ fmap (+2) z 

At first I misunderstood your question. The pattern <$> <*> much more common than your <*> <$> , the following addresses that ... may be useful to other people.

f <$> y <*> z can also be written liftA2 fyz , and liftA2 is much easier to understand than the equivalent <*> .

 liftA2 :: (a -> b -> c) -> fa -> fb -> fc 

What he does is he takes the function of the combiner by value and produces from it the function of the combiner on the containers. It is a bit like zipWith , with the exception of the list instance, it not only combines each element in list a with the corresponding element in list b , but combines each element in list a once with all elements in list b and combines the results.

 Prelude> Control.Applicative.liftA2 (+) [0,10..30] [0..3] [0,1,2,3,10,11,12,13,20,21,22,23,30,31,32,33] 
+3
source

Source: https://habr.com/ru/post/1266763/


All Articles