Closed neko-kai closed 6 years ago
Added .addImplicit
method which is sort of a half-measure:
val mod = new ModuleDef {
addImplicit[Ordering[Int]]
// same as make[Ordering[Int]].from(implicitly[Ordering[Int]])
}
The reason for not going all the way is that it's actually not modular to require implicit implementations to be available in scope when binding classes depending on implicits.
Requiring implicits to be explicitly bound to implementations seems like a more consistent position to me, since If we'd have wanted to directly depend on implementations, we may as well not have bothered with distage
at all.
We could leave out explicit bindings of type classes in DI context, so instead of this:
We could write this:
For static (macro) provisioner the change is easy, since generated code has access to the scope and implicits of enclosing ModuleDef: see https://github.com/kaishh/izumi-r2/pull/1/files#diff-994a658fc1f58f4d7831c3113087b6d9 , so we just need to replace parameters with
implicitly[X]
and not request them from DI context.But, there are problems with this approach:
What we can do instead, is capture implicit parameters and inject them into context with a macro, transforming a call such as this:
into something like this
That way, implicits are also managed by DI and we can guarantee type class coherence (which is a desirable feature in global context and which scala itself currently does not guarantee) – if there are two different instances for the same type class, application will fail to start and report conflicting bindings
However, there is an issue with deciding instance equality, for
implicit val
s everything is fine, but forimplicit def
s every implicit summon will generate a new instance, these instances are not equivalent:That means we can't, in general, decide type class instance equivalence and guarantee coherence.
But, we can use some heuristics instead, for example:
ExecutionContext
) using.equals
, but always assume that implicits with type parameters (such asMonad[F]
) are type classes and are the same if their type parameters are the same and compare them only by usingSafeType.equals
.AnonymousClass
es areimplicit vals
and compare them using.equals
, but when.getClass.isAnonymousClass
is true, compare them only by using.getClass.equals
– this heuristic is kinda faulty – while most library authors use traits to define type classses and putnew X { ... }
anonymous classes in implicit defs, sometimes they also create named classes for instances. Also, this is less portable –scala.js
doesn't have.isAnonymousClass
method onClass
.