Search code examples
scalaoutputmutable

Why am I getting an (inconsistent) compiler error when using a for comprehension with a result of Try[Unit]?


A common pattern I have in my code base is to define methods with Try[Unit] to indicate the method is doing something (like writing a transaction long to a SaaS REST endpoint) where there is no interesting return result and I want to capture any and emit any errors encountered to stdout. I have read on another StackOverflow thread this is entirely acceptable (also see 2nd comment on the question).

I am specifically and explicitly avoiding actually throwing and catching an exception which is an extraordinarily expensive activity on most JVM implementations. This means any answer which uses the Try() idiom won't help resolve my issue.

I have the following code (vastly simplified from my real life scenario):

def someMethod(transactionToLog: String): Try[Unit] = {
  val result =
    for {
      _ <- callToSomeMethodReturningATryInstance
    } yield Unit
  result match {
    case Success(_)
      //Unit
    case Failure(e) ->
      println(s"oopsie - ${e.getMessage}")
  }
  result
}

Sometimes this code compiles just fine. Sometimes it doesn't. When it doesn't, it gives me the following compilation error:

Error:(row, col) type mismatch;
 found   : scala.util.Try[Unit.type]
 required: scala.util.Try[Unit]
    result
    ^

Sometimes the IntelliJ syntax highlighter shows the code as fine. Other times it shows an error (roughly the same as the one above). And sometimes I get the compiler error but not a highligher error. And other times it compiles fine and I get only a highlighter error. And thus far, I am having a difficult time finding a perfect "example" to capture the compiler error.

I attempted to "cure" the issue by adding .asInstanceOf[Unit] to the result of the for comprehension. That got it past the compiler. However, now I am getting a runtime exception of java.lang.ClassCastException: scala.Unit$ cannot be cast to scala.runtime.BoxedUnit. Naturally, this is infinitely worse than the original compilation error.

So, two questions:

  1. Assuming Try[Unit] isn't valid, what is the Scala idiomatic (or even just preferred) way to specify a Try which returns no useful Success result?

  2. Assuming Try[Unit] is valid, how do I get past the compilation error (described above)?


SIDENOTE:
I must have hit this problem a year ago and didn't remember the details. I created the solution below which I have all over my code bases. However, recently I started using Future[Unit] in a number of places. And when I first tried Try[Unit], it appeared to work. However, the number of times it is now causing both compilation and IDE highlighting issues has grown quite a bit. So, I want to make a final decision about how to proceed which is consistent with whatever exists (or is even emerging) and is Scala idiomatic.

package org.scalaolio.util

/** This package object serves to ease Scala interactions with mutating
 *  methods.
 */
package object Try_ {
  /** Placeholder type for when a mutator method does not intend to
   *  return anything with a Success, but needs to be able to return a
   *  Failure with an unthrown Exception.
   */
  sealed case class CompletedNoException private[Try_] ()

  /** Placeholder instance for when a mutator method needs to indicate
   *  success via Success(completedNoExceptionSingleton)
   */
  val completedNoExceptionSingleton = new CompletedNoException()
}

Solution

  • Replace yield Unit with yield ().

    Unit is a type - () is a value (and the only possible value) of type Unit.

    That is why for { _ <- expr } yield Unit results in a Try[Unit.type], just as yield String would result in a Try[String.type].