Let's hold on for a second and recap what we just did.
We made a small sacrifice and increased the complexity of the return type of our original functions to some "common denominator" type. This sacrifice is rather small because in our example, as well as in real life, this is usually done by just lifting the original functions into their proper context.
The signatures we came up with look a little awkward, but this is partly because we started to develop them as concrete implementations. In fact, the user-facing API of our fishing component should be similar to the following snippet straight from the beginning, if implemented in a more abstract way:
abstract class FishingApi[F[_]: Monad] {
val buyBait: String => F[Bait]
val castLine: Bait => F[Line]
val hookFish: Line => F[Fish]
def goFishing(bestBaitForFish: F[String]): F[Fish]...