4

2 different examples, the first one works:

  import cats.syntax.either._
  val e = 10.asRight[String]
  def i2s(i:Int):String = i.toString
  e.map(i => List(i2s(i))) //using explicit parameter
  e.map(List(i2s(_)))   //using no-name _ parameter

Now the same example with Option is not compiled:

e.map(Option(i2s(_))) 

The error:

Error:(27, 15) type mismatch;
 found   : Option[Int => String]
 required: Int => ?
  e.map(Option(i2s(_)))

With explicit parameter it works fine:

e.map(i => Option(i2s(i)))

In both cases apply method is invoked with List and Option. List.apply signature:

 def apply[A](xs: A*): List[A] = ???

Option.apply signature:

 def apply[A](x: A): Option[A]

Please explain the difference.

Chaitanya
  • 3,590
  • 14
  • 33
Alexandr
  • 9,213
  • 12
  • 62
  • 102

2 Answers2

4

Both of your List examples compile but they don't mean the same thing and don't produce the same results.

e.map(i => List(i2s(i))) //res0: scala.util.Either[String,List[String]] = Right(List(10))
e.map(List(i2s(_)))      //java.lang.IndexOutOfBoundsException: 10

The 1st is easy to understand, so what's going on with the 2nd?

What's happening is that you're using eta expansion to create an Int => String function from the i2s() method. You then populate a List with that single function as the only element in the list, and then try to retrieve the value at index 10, which doesn't exist, thus the exception.

If you change the 1st line to val e = 0.asRight[String] then the exception goes away because something does exist at index 0, the function that was just put in there.

This compiles because a List instance will accept an Int as a parameter (via the hidden apply() method), but an Option instance does not have an apply() method that takes an Int (*) so that can't be compiled.

(*) The Option object does have an apply() method, but that's a different animal.

jwvh
  • 50,871
  • 7
  • 38
  • 64
2

There are multiple things at play here as to why your first example with List[A] works. First, let's look at the expansion that happens on the expression:

val res: Either[String, Int => String] = 
  e.map[Int => String](List.apply[Int => String](((x$1: Int) => FlinkTest.this.i2s(x$1))));

Notice two things:

  • The expansion of the lambda expression happens inside List.apply, and perhaps not as you expected, for it to be outside of List.apply, like this:

    e.map(i => List(i2s(i))
    
  • The return type from .map is somehow not Either[String, List[Int => String]], but Either[String, Int => String]. This is due to the fact that in it's hierarchy chain, List[A] extends PartialFunction[Int, A], thus allowing it to transform the result into a function type.

This doesn't work for Option[A], as it doesn't extend PartialFunction anywhere in it's type hierarchy.

The key takeaway here is that the expansion of the lambda expression doesn't work as you expect, as List(i2s(_)) expands to List(i2s(x => i2s(x)) and not List(i => i2s(i)). For more on underscore expansion, see What are all the uses of an underscore in Scala?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321