Function composition different behaviour of andThen and compose

I’m currently going through Scala with Cats and enjoying it a lot.
In Exercise 4.1.2 (Getting Func-y) we are tasked to implement `map` using the methods `pure` and `flatMap` only.

The code for reference:

``````import scala.language.higherKinds

trait Monad[F[_]] {
def pure[A](a: A): F[A]

def flatMap[A, B](value: F[A])(func: A => F[B]): F[B]

def map[A, B](value: F[A])(func: A => B): F[B] =
???
}
``````

My thinking was as follows:
For the first parameter of `flatmap` we just pass `value`.
For the second parameter, we have the function `func: A => B` and a method `pure`, which, after eta expansion, has the form `pure: A => F[A]`. Therefore, we compose `pure` with `func`, i.e. we pass `pure _ compose func`, where `pure _` eta expands `pure`.

`map` would then look like this:

``````def map[A, B](value: F[A])(func: A => B): F[B] =
flatMap(value)(pure _ compose func)
``````

However, this didn’t work and produced the following error:

``````[error] 7:35: type mismatch;
[error]   A => B|scala.Nothing
[error]     flatMap(value)(pure _ compose func)
[error]                                   ^
[info] A => B <: ? => Nothing?
[info] false
[error] 6:30: parameter value func in method map is never used
[error]   def map[A, B](value: F[A])(func: A => B): F[B] =
[error]                              ^
[error] two errors found
[error] (Compile / compileIncremental) Compilation failed
``````

To my surprise, using `func andThen pure` for the second parameter does compile.

`map` defined like this compiles:

``````def map[A, B](value: F[A])(func: A => B): F[B] =
flatMap(value)(func andThen pure)
``````

I’m surprised because I assumed that for two functions `f: A => B` and `g: B => C`, the following would hold:

`f andThen g` is equivalent to `a => g(f(a))` and
`g compose f` is equivalent to `a => g(f(a))`,
therefore `f andThen g` and `g compose f` are equivalent.

What’s the reason that this doesn’t hold in general and in this example?

I used scala version 2.13.8 above.

I thought, maybe the problem relates to the eta expansion of the `pure` method. I found several questions relating to `compose` and `andThen` methods as well as eta expansion, but they didn’t answer my question.

I found a scala 3 reference entry about Automatic Eta Expansion, so I tried my luck in scala 3.2.1:

``````trait Monad[F[_]]:
def pure[A](a: A): F[A]

def flatMap[A, B](value: F[A])(func: A => F[B]): F[B]

def map[A, B](value: F[A])(func: A => B): F[B] =
flatMap(value)(pure compose func)
``````

Notice, how we don’t need the underscore after `pure` anymore (it will be deprecated at some point in the future) because of the automatic eta expansion mentioned in the scala 3 reference article above.

However, this also throws an error:

``````-- [E007] Type Mismatch Error: --------------------------------------------------------
7 |    flatMap(value)(pure compose func)
|                   ^^^^^^^^^^^^^^^^^
|                   Found:    A => F[Any]
|                   Required: A => F[B]
|
|                   where:    F is a type in trait Monad with bounds <: [_] =>> Any
``````

(In the where clause, I don’t understand what kind of upper bound `[_] =>> Any` should be.)

But, again, `map` using `func andThen pure` compiles.

Based on the error message, I now believe that the compiler can somehow not infer the correct type because… of the type parameters? I don’t know.

>Solution :

Sometimes eta-expansion is sometimes confusing for the compiler and you have to clarify things yourself.

In Scala 2 I managed to make your code compile with:

``````def map[A, B](value: F[A])(func: A => B): F[B] =
flatMap(value)((pure[B](_)).compose(func))
``````

because `method _` doesn’t always work and `method(_)` is more in your face (you still have to define `[B]` to avoid inferring it to something silly like `Any` or `Nothing`).

In Scala 3 it works slightly better:

``````def map[A, B](value: F[A])(func: A => B): F[B] =
flatMap(value)((pure[B] _).compose(func))
``````

or even

``````def map[A, B](value: F[A])(func: A => B): F[B] =
flatMap(value)(pure[B] compose func)
``````

works – you only have to provide `[B]` explicitly so that it wont attempt to combine `Nothing => F[Nothing]` with `A => B`. Currently it cannot be avoided as compiler:

• tries to infer types for `pure` before calling `compose` on eta-expanded method
• since no restriction yet applies, it infers it to `Nothing`
• only then it can infer types on `.compose` which takes `A => B` value
• however it already inferred that it should be something that is `? => Nothing`

so this particular problem comes from compiler not inferring whole expression at once but doing it in chunks.