When map over a function is useful when you have andThen
Asked Answered
S

1

7

With Scalaz, a function can be mapped over another function. When would I want to use map over andThen? Is there a clear advantage using map? Thanks

For example,

val f: Int => Int = (a) => a + 10

val g: Int => Int = (a) => a * 100

(f map g map {_*3})(10) == (f andThen g andThen {_*3})(10)  // true
Shanda answered 2/7, 2016 at 9:42 Comment(2)
Well map works on any functor, not just functions.Groveman
I was thinking about that too. So I can map over a list of option and list of functor but I couldn't figure how to create a list of functors.Shanda
F
11

Setting aside implementation details for a moment, map is andThen for functions (under the functor instance for A => ?), and it doesn't really make a lot of sense to talk about preferring one over the other if we're talking about functions specifically and not some higher level of abstraction.

What methods like map (and type classes like Functor more generally) allow us to do is abstract over specific types or type constructors. Suppose we want to write a incrementResult method that works on both A => Int or Kleisli[Option, A, Int], for example. These types don't have anything in common in terms of inheritance (short of AnyRef, which is useless), but A => ? and Kleisli[Option, A, ?] are both functors, so we could write this:

import scalaz._, Scalaz._

def incrementResult[F[_]: Functor](f: F[Int]): F[Int] = f.map(_ + 1)

And then use it like this (note that I'm using kind-projector to simplify the type syntax a bit):

scala> val plainOldFuncTriple: Int => Int = _ * 3
plainOldFuncTriple: Int => Int = <function1>

scala> val optionKleisliTriple: Kleisli[Option, Int, Int] = Kleisli(i => Some(i * 3))
optionKleisliTriple: scalaz.Kleisli[Option,Int,Int] = Kleisli(<function1>)

scala> val f = incrementResult[Int => ?](plainOldFuncTriple)
f: Int => Int = <function1>

scala> val k = incrementResult[Kleisli[Option, Int, ?]](optionKleisliTriple)
k: scalaz.Kleisli[Option,Int,Int] = Kleisli(<function1>)

scala> f(10)
res0: Int = 31

scala> k(10)
res1: Option[Int] = Some(31)

In this case specifically there are better ways to implement this operation, but it shows the general idea—we couldn't write a single method that works for both ordinary functions and Kleisli arrows using andThen, but we can with the extra level of abstraction that map gives us.

So to answer your question—you'd use map if you want to abstract over all type constructors that have a functor instance, but if you're working specifically with functions, map is andThen, and—as long as we're still setting aside implementation details—it doesn't matter which you choose.


Footnote: the map that Scalaz's syntax package gives you for values of types that have functor instances is implemented as an extension method, so there's a tiny bit of overhead (both at compile time and runtime) involved in using map instead of andThen on a function. If you're only working with functions and don't need the extra abstraction, then, you might as well go with andThen.

Fret answered 2/7, 2016 at 19:13 Comment(3)
We can write a single method that works for both using andThen: def incrementResult[~>[_, _] : Compose, A](f: A ~> Int): A ~> Int = f andThen (_ + 1)Ovida
@JulienRichard-Foy Sure (that's what I meant by "better ways to implement this operation), but that's kind of a distraction since that andThen is not the andThen on Function1, which is what the OP is asking about.Fret
@JulienRichard-Foy how do I fix this? value andThen is not a member of type parameter ~>[A,Int]Shanda

© 2022 - 2024 — McMap. All rights reserved.