33

Suppose I need to convert Option[Int] to Either[String, Int] in Scala. I'd like to do it like this:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold(Left("No number")) {x => Right(x)}

Unfortunately the code above doesn't compile and I need to add type Either[String, Int] explicitly:

ox.fold(Left("No number"): Either[String, Int]) { x => Right(x) }

Is it possible to convert Option to Either this way without adding the type ?
How would you suggest convert Option to Either ?

Xavier Guihot
  • 54,987
  • 21
  • 291
  • 190
Michael
  • 41,026
  • 70
  • 193
  • 341

3 Answers3

49

No, if you do it this way, you can't leave out the type.

The type of Left("No number") is inferred to be Either[String, Nothing]. From just Left("No number") the compiler can't know that you want the second type of the Either to be Int, and type inference doesn't go so far that the compiler will look at the whole method and decide it should be Either[String, Int].

You could do this in a number of different ways. For example with pattern matching:

def foo(ox: Option[Int]): Either[String, Int] = ox match {
  case Some(x) => Right(x)
  case None    => Left("No number")
}

Or with an if expression:

def foo(ox: Option[Int]): Either[String, Int] =
  if (ox.isDefined) Right(ox.get) else Left("No number")

Or with Either.cond:

def foo(ox: Option[Int]): Either[String, Int] =
  Either.cond(ox.isDefined, ox.get, "No number")
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • `ox.map(Right(_)).getOrElse(Left("No number"))` works as well but creates an intermediate `Option` instance. – knutwalker Jan 10 '16 at 15:03
  • 20
    And there is also `ox.toRight("No Number")` but since this method does not have an explicit return type, it gets inferred to `Serializable with Product with Either[String, Int]` – knutwalker Jan 10 '16 at 15:12
  • @knutwalker Oh, thanks. I like your `toRight` solution. I will just add the type: `ox.toRight("No Number"): Either[String, Int]`. – Michael Jan 10 '16 at 15:19
16

I am not sure which version of Scala you were using at the time. Currently, with Scala 2.12.6 there's no compilation problems with your code like this:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.toRight("No number")

One other point I'd like to make is that folding (while it's my preferred method of collapsing just about anything that has a fold method on it) quite often requires help with type parameters. There's two ways the compiler can type check an expression, either it can infer the type parameters or it can simply find them explicitly defined.

In your example, if you're trying to fold an option like so:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold(Left("No number") : Either[String, Int])(x => Right(x))

You're explicitly providing type information about the first argument, which in turn can be then used to infer the type parameter of fold. You're aiding the type inference mechanism.

On the other hand, you could simply just explicitly provide the type parameter for fold like so:

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold[Either[String, Int]](Left("No number"))(x => Right(x))

Now your actual (value-level) arguments are not littered with superfluous type information, and there's no type inference going on when the compiler looks at it, it can tell right away what fold's type parameter is, as it's been explicitly provided. Use the square brackets to specify the type parameter explicitly.

One more point, regarding x => Right(x) here you're practically creating a new function literal that does nothing other than pass x into the apply method of the Right case class's companion object. You already have a function of the appropriate shape available. It takes x and returns a Right(x). It is the apply method. You can refer to it (pass it) directly.

def foo(ox: Option[Int]): Either[String, Int] =
  ox.fold[Either[String, Int]](Left("No number"))(Right.apply)
Peter Perháč
  • 20,434
  • 21
  • 120
  • 152
2

The reason why type annotation is necessary is because of the way type inference works in Scala 2 for multiple parameter lists where

  • parameter list are considered one at a time, and
  • constraints accumulated in first parameter lists are applied to the next parameter list.

Consider signature of Option#fold

def fold[B](ifEmpty: => B)(f: A => B): B

where we see type parameter B and two parameter lists. Now type of argument provided to the first parameter list is Left[String,Nothing] because

scala> Left("No number")
val res0: scala.util.Left[String,Nothing] = Left(No number)

which means type parameter B is inferred to be Left[String,Nothing], which in turn constrains the expected type of the argument provided to the second parameter list to be a function of Left return type

A => Left[String,Nothing]

however we are providing a function of Right return type, therefore it errors with type mismatch

Welcome to Scala 2.13.3 (OpenJDK 64-Bit Server VM, Java 1.8.0_252).
Type in expressions for evaluation. Or try :help.

scala> def foo(ox: Option[Int]): Either[String, Int] =
     |   ox.fold(Left("No number")) {x => Right(x)}
         ox.fold(Left("No number")) {x => Right(x)}
                                               ^
On line 2: error: type mismatch;
        found   : scala.util.Right[Nothing,Int]
        required: scala.util.Left[String,Nothing]

Note that Scala 3 (Dotty) brings improvements to type inference so your snippet would work out of the box without having to provide explicit type annotation

Starting dotty REPL...
scala> def foo(ox: Option[Int]): Either[String, Int] =
     |   ox.fold(Left("No number")) {x => Right(x)}
     |
def foo(ox: Option[Int]): Either[String, Int]
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
  • The scala 2 type inference over multiple parameter list is key to this question. The other answers missed that. This deserves to be the accepted answer. – montrivo Dec 29 '20 at 16:39