Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

x <*> y <$> z in Haskell

I'm trying to understand some Haskell source code, and I encountered this structure some times:

x <*> y <$> z

e.g.

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

Can somebody explain this structure to me? I get that it translates to fmap a (+ a + 1), but I can't make the connection

like image 483
hgiesel Avatar asked Apr 15 '17 15:04

hgiesel


People also ask

How do you define a function in Haskell?

Like other languages, Haskell does have its own functional definition and declaration. Function declaration consists of the function name and its argument list along with its output. Function definition is where you actually define a function.

What is Haskell's case expression?

Haskell's case expressionprovides a way to solve this problem. Indeed, the meaning of pattern matching in function definitions is specified in the Report in terms of case expressions, which are considered more primitive. In particular, a function definition of the form: fp11... p1k=e1 fpn1... pnk=en

How do you make a functor pair in Haskell?

Haskell doesn't allow a type Pair a = (a, a) to be a functor instance, so we define our own Pair type instead. data Pair a = Pair a a deriving Show instance Functor Pair where fmap f (Pair x y) = Pair (f x) (f y)

Is it possible to quantify a variable in Haskell?

It is only a reserved word within types. Type variables in a Haskell type expression are all assumed to be universally quantified; there is no explicit syntax for universal quantification, in standard Haskell 98/2010. For example, the type expression a -> a denotes the type forall a. a ->a.


2 Answers

Let's start with:

x <*> y <$> z

Adding parentheses, it becomes:

(x <*> y) <$> z

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

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

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

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

Combining the last few results, we get:

g d ~ a -> b
g ~ (->) a
d ~ b

x :: a -> c -> b
y :: a -> c
x <*> y :: a -> b

Therefore:

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

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

x <*> y <$> z
(\r -> x r (y r)) <$> 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
like image 200
duplode Avatar answered Oct 18 '22 04:10

duplode


So in x <*> y <$> z, i.e. fmap (x<*>y) z, you apply the function x<*>y over the functor-value z. The <*> actually knows nothing about the fmapping – the two operators work on completely separate functors! That the first important thing to realise here.

The next is that, if the result of x<*>y is a function, then the Applicative instance of the <*> is actually the function functor. I wish people would stop using that so much, because it's really one of the more confusing instances and generally not the nicest abstraction to choose.

Concretely, f<*>g is just a clever way of composing the functions f and g while also passing the initial input directly to f. It works thus:

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

i.e.

(<*>) :: (x ->(a -> b)) -> (x -> a) -> (x -> b)
       ≡ (x -> a -> b)  -> (x -> a) ->  x -> b
(f <*> g) x = f x $ g x

In terms of data flow, it's this operation:

────┬─────▶ f ──▶
    │       │
    └─▶ g ──┘

I would rather express this with arrow combinators:

     ┌───id──┐
────&&&     uncurry f ──▶
     └─▶ g ──┘

so f<*>g ≡ id &&& g >>> uncurry f. Granted, that is nowhere as compact, in fact more verbose than the explicit lambda version \x -> f x $ g x, which frankly would probably be the best. However, the arrow version is the most general version of the three and arguably expresses best what's going on. The main reason it's so verbose is that currying does not work in out favour here; we might 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 the example, we have

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

I first misread your question. The pattern <$> <*> much more common than your <*> <$>, the following adresses that... perhaps useful for other people.

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

liftA2 :: (a -> b -> c) -> f a -> f b -> f c

What it does is, it takes a combiner-function on values and produces from it a combiner-function on containers. It's a bit similar to zipWith except for the list instance, it not only combines each element in the a list with the corresponding element in the b list, but combines each element in the a list once with all elements in the b list, and concatenates 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]
like image 36
leftaroundabout Avatar answered Oct 18 '22 04:10

leftaroundabout