2

How can I interpret this expression?

:t (+) <$> (+3) <*> (*100)

Since <$> and <$> have the same priority and are left-associative. I think it would be same to ((+) <$> (+3)) <*> (*100). However, I have no clue what it does. In Learn You a Haskell for Great Good, it is mentioned that

When we do (+) <$> (+10) <*> (+5), we're using + on the future return values of (+10) and (+5), and the result is also something that will produce a value only when called with a parameter.

It sounds to be right-associative. Could anyone explain it?

Micha Wiedenmann
  • 19,979
  • 21
  • 92
  • 137
helloword
  • 53
  • 5
  • 1
    I'm not sure what you mean by "it sounds to be right-associative", I don't see anything that implies that in the quoted sentence, and your previous sentence reasoning about how this is evaluated is correct. As for how this expression is evaluated, in the context of the Applicative instance for "functions with a fixed argument type" see [this old question](https://stackoverflow.com/questions/54961639/applicative-functor-evaluation-is-not-clear-to-me). (It's arguably a duplicate, but perhaps your actual question is slightly different - it's unclear to me.) – Robin Zigmond Jan 03 '23 at 22:05
  • 1
    I find it simpler to ignore the details of how `<$>` and `<*>` are defined, and simply remember as an idiom that `f <$> g1 <*> g2 <*> ... <*> gn` is the function `\x -> f (g1 x) (g2 x) ... (gn x)`. That is, it's a function which applies each `g` to the argument before passing the `g` results to `f`. – chepner Jan 04 '23 at 00:57
  • @RobinZigmond Thanks for the reference; that is also quite helpful. I have wrongly implied the left associativity due to the sentence "use + on the future return values of (+10) and (+5)". I wrongly assumed that (+10) and (+5) were evaluated first, then we applied (+) to both results. – helloword Jan 04 '23 at 19:15

1 Answers1

7

The expression:

(+) <$> (+3) <*> (*100)

will use the Functor and Applicative instances for a function in Haskell. Indeed, a -> b is short for (->) a b with (->) a type constructor. So we can define instances as Functor (->) a to work with a function. The Functor [src] and Applicative [src] instances are defined as:

instance Functor ((->) r) where
    fmap = (.)

instance Applicative ((->) r) where
    pure = const
    (<*>) f g x = f x (g x)
    liftA2 q f g x = q (f x) (g x)

The fmap on a function thus acts as a "post-processor": it convers a function a -> b into a function a -> c if the first operand is a function b -> c by applying that function on the result of the first function.

The Applicative will take two functions f :: a -> b -> c and g :: a -> b and thus construct a function that maps x on f x (g x).

If we thus look at the expression:

(+) <$> (+3) <*> (*100)

Then this is equivalent to:

(<*>) (fmap (+) (+3)) (*100)

The fmap thus is equivalent to (.), so this is:

(<*>) ((+) . (+3)) (*100)

or:

(<*>) (\x -> ((x+3) +)) (*100)

which is equivalent to:

\y -> (\x -> ((x+3) +)) y ((*100) y)

We can simplify this to:

\y -> (y+3) +) (y*100)

and thus:

\y -> (y+3) + (y*100)

It thus produces a function will map y to y + 3 + (y * 100). It will thus apply y to both (+3) and (*100) and then adds these together because of the (+) before the <$>.

The rule in general is thus that:

g <$> f1 <*> f2 <*> … <*> fn

with fi :: a -> bi and g :: b1 -> b2 -> … -> bn -> c, then it creates a function that will map a variable x to g (f1 x) (f2 x) … (fn x).

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555