3

I am trying to apply a function: A => B to a Result[A] to get a Result[B] and am getting a weird type error.

 found   : Product with Serializable with this.Result[_ <: B]
 required: this.Result[B]

I'm not sure how to resolve this issue. It looks like scala is trying to convert the output of the function to be a string (serializable) because it can't find a conversion between Success/Failure and Result?

The simplified code is below. I tried attaching explicit type information and the code would not compile. Any help would be appreciated!

sealed trait Result[A]{ 
    def map[B](f: A => B) = this match{
        case Success((a, rest)) => Success((f(a), rest))
        case Failure(m) => Failure(m)
    }
}
case class Success[A](result: (A, List[Char])) extends Result[A]
case class Failure[A](message: String) extends Result[A]

object Utils{
    def map[A,B](r: Result[A], f: A => B):Result[B] = {
        r.map(f)
    }
}

2 Answers 2

3

To explain the original issue: you don't specify return type of Result.map, so Scala has to infer it. To do so, it looks at both branches. The return type of the first one is Success[B]. The return type of the second is Failure[Nothing], because Scala has no reason to assume you mean Failure[B](m) there!

So now it has to find the type which both Success[B] and Failure[Nothing] fit (their least upper bound). Well, they both extend Product and Serializable (because they are case classes). They also both extend Result, but with different type parameters. The least upper bound of Nothing and B is B, so they both extend Result[_ <: B]. And that's the type you end up for map: Product with Serializable with Result[_ <: B].

Note that by applying Andy Hayden's answer, you will get Product with Serializable with Result[B] instead, not Result[B] which is probably what you want. To do that, either specify the return type explicitly (which works even without covariance, hopefully you can now see why), or make Result extend Product with Serializable.

Sign up to request clarification or add additional context in comments.

Comments

1

As pointed out by Alexey, you should specify the return type of Result's map (otherwise the Failure type is ambiguous, although you can specify the Failure type instead Failure[B]):

sealed trait Result[A]{ 
    // specify the return type Result[B]
    def map[B](f: A => B): Result[B] = this match{
        case Success((a, rest)) => Success((f(a), rest))
        case Failure(m) => Failure(m)  // alternatively use Failure[B](m) here
    }
}
case class Success[A](result: (A, List[Char])) extends Result[A]
case class Failure[A](message: String) extends Result[A]

object Utils{
    def map[A,B](r: Result[A], f: A => B):Result[B] = {
        r.map(f)
    }
}

This will give a Failure the correct type:

scala> val f = Failure[Int]("oops")
Failure("oops"): Failure[Int]

scala> Utils.map(f, {i: Int => 4})
Failure("oops"): Result[Int]

vs (with the covariant solution below):

scala> Utils.map(f, {i: Int => 4})
Failure(oops): Product with Serializable with Result[_37] forSome { type _37 <: Int }

which is not what you want!


Originally I suggested that you make your types covariant (the +A):

sealed trait Result[+A]{ 
    def map[B](f: A => B) = this match{
        case Success((a, rest)) => Success((f(a), rest))
        case Failure(m) => Failure(m)
    }
}
case class Success[A](result: (A, List[Char])) extends Result[A]
case class Failure[A](message: String) extends Result[A]

object Utils{
    def map[A,B](r: Result[A], f: A => B):Result[B] = {
        r.map(f)
    }
}

See this great blogpost for more discussion.

To quote part of the post:

Subtype Relationships Assume that class Orange extends Fruit holds.
If class Box[A] is declared, then A can be prefixed with + or -.

A without annotation is invariant, i.e.:
Box[Orange] has no inheritance relationship to Box[Fruit].

+A is covariant, i.e.: Box[Orange] is a subtype of Box[Fruit].
var f: Box[Fruit] = new Box[Orange]() is allowed.

-A is contravariant, i.e.:
Box[Fruit] is a subtype of Box[Orange].
var f: Box[Orange] = new Box[Fruit]() is allowed.

2 Comments

I knew it had something to do with covariance and contravariance. I'm newer to scala and it is something I'm still not clear on. Thanks a bunch, the blog post was super helpful.
"Essentially this tells scala that Success[B] and Failure[B] are subtypes of Result[B]. " This is completely wrong, Success[A](...) extends Result[A] does that. You should also make both Success and Failure covariant as well.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.