Dotty: Type inference issue with intersection types

Created on 25 Feb 2020  Â·  12Comments  Â·  Source: lampepfl/dotty

minimized code

trait Has[_]

trait A
trait B
trait C

trait ZLayer[-RIn, +E, +ROut]

object ZLayer {
  def fromServices[A0, A1, B](f: (A0, A1) => B): ZLayer[Has[A0] with Has[A1], Nothing, Has[B]] =
    ???
}

val live: ZLayer[Has[A] & Has[B], Nothing, Has[C]] =
  ZLayer.fromServices { (a: A, b: B) =>
    new C {}
  }

Compilation output

// type mismatch
// found:    (A, B) => C
// required: (A, A) => C

expectation

I would have expected this to compile because live expected a ZLayer with the first parameter of Has[A] with Has[B], which corresponds to a function (A, B) => C like I am providing. But the compiler seems to be expecting the first part of the intersection type twice. If I change the type signature of the first parameter in the return type of fromServices to Has[B] with Has[A] then I get an error that the compiler expected (B, B) => C. This issue also appears to exist on Scala 2, reported at https://github.com/scala/bug/issues/11898.

bug

Most helpful comment

Yeah I guess I'll close since there's nothing immediately actionable here, but I'll keep this in mind.

I managed to find a satisfying solution, part of the latest nightly: https://github.com/lampepfl/dotty/pull/8635 ! :)

All 12 comments

this works if Has is not invariant, is that an issue?

Yes Has needs to be invariant.

~http://dotty.epfl.ch/docs/reference/new-types/intersection-types-spec.html - according to this, intersection types only compose within a covariant constructor~

sorry, ignore the above

I don't think we are looking to compose intersection types in that sense. We explicitly don't want Has[A] & Has[B] to be composed to Has[A & B]. All we want is for it to compile when we say we want Has[A] with Has[B] and we provide a value consistent with that. To highlight the strangeness here, this works fine if I use an intermediate value:

object Example extends App {

  trait Has[_]

  trait A
  trait B
  trait C

  trait ZLayer[-RIn, +E, +ROut]

  object ZLayer {
    def fromServices[A0, A1, B](f: (A0, A1) => B): ZLayer[Has[A0] with Has[A1], Nothing, Has[B]] =
      ???
  }

  val live: ZLayer[Has[A] & Has[B], Nothing, Has[C]] = {
    val layer = ZLayer.fromServices { (a: A, b: B) =>
      new C {}
    }
    layer
  }
}

Why is it that by providing additional type information in the form of an expected return type that is the actual return type that would be inferred in the absence of additional information causing this not to compile?

ah yes, sorry, I've moderated my comment

No worries, we're all trying to figure this out!

I've left some comments on the corresponding scalac issues which are also applicable here: https://github.com/scala/bug/issues/11898#issuecomment-591403470

@smarter Thank you! Should we close this since it doesn't sound like there is a path to addressing front he Dotty side or do you like to keep these types of issues open in case you want to come back to at some point?

Yeah I guess I'll close since there's nothing immediately actionable here, but I'll keep this in mind.

Also of interest: I assume you're using this weird intersection in a contravariant position to encode a union of thing, in that case you could also directly use a union in a covariant position if you were dotty-only:

trait Has[A]

trait A
trait B
trait C

trait ZLayer[+RIn, +E, +ROut]

object ZLayer {
  def fromServices[A0, A1, B](f: (A0, A1) => B): ZLayer[Has[A0] | Has[A1], Nothing, Has[B]] =
    ???
}

val live: ZLayer[Has[A] | Has[B], Nothing, Has[C]] = {
  val layer = ZLayer.fromServices { (a: A, b: B) =>
    new C {}
  }
  layer
}

This infers correctly (but that's also unspecified)

@smarter Unfortunately Has is morally covariant, it does encode an intersection, not a union – the type says you can retrieve values of A and A1 from a dynamically-typed map, but not values of any other types. It could probably be modeled without intersections in dotty, as a match type traversing a tuple of allowed keys.

Yeah I guess I'll close since there's nothing immediately actionable here, but I'll keep this in mind.

I managed to find a satisfying solution, part of the latest nightly: https://github.com/lampepfl/dotty/pull/8635 ! :)

Was this page helpful?
0 / 5 - 0 ratings