Reputation: 44957
Suppose that I have some typeclass
trait FooBar[X]
and an instance of FooBar[Int]
:
given intIsFooBar: FooBar[Int] = new FooBar {}
Now, suppose that I have an interface Intf
that has some member type A
and also guarantees that there is a given FooBar[A]
:
trait Intf:
type A
given aIsFoobar: FooBar[A]
Now, I have the type Int
, and I have a FooBar[Int]
, but how do I actually implement this interface for Int
?
If I try
class IntImpl() extends Intf:
type A = Int
given aIsFoobar: FooBar[A] = summon
then I get "Infinite loop in function body IntImpl.aIsFoobar" errors, because the summon
seems to see the aIsFoobar
instead of intIsFooBar
.
If I try to summon
the instance in some auxiliary helper variable, like so:
class IntImpl() extends Intf:
type A = Int
private final val _aIsFoobar: FooBar[A] = summon
given aIsFoobar: FooBar[A] = _aIsFoobar
then I run into initialization order issues: aIsFoobar
turns out to be null
, and my application crashes with NullPointerExceptions
, which is kinda ridiculous.
I've also tried export
, but none of this works:
export FooBar[Int] as aIsFoobar // doesn't compile, invalid syntax
How do I make "the canonical" FooBar[Int]
available as the aIsFoobar
given member?
Full code:
trait FooBar[X]
given intIsFooBar: FooBar[Int] = new FooBar {}
trait Intf:
type A
given aIsFoobar: FooBar[A]
object IntImpl extends Intf:
type A = Int
given aIsFoobar: FooBar[A] = summon
Upvotes: 5
Views: 667
Reputation: 44957
Since Scala 3.6, there are now Deferred Givens, which solve exactly the problem described above. Here is what the code looks like with compiletime.deferred
:
trait FooBar[X] {
def canonical: X
}
given intIsFooBar: FooBar[Int] with {
def canonical: Int = 42
}
trait Intf:
type A
given aIsFoobar: FooBar[A] = compiletime.deferred
object IntImpl extends Intf:
type A = Int
// Nothing has to be done here, compiler will generate aIsFoobar for Int
@main def example(): Unit =
println(IntImpl.aIsFoobar.canonical) // prints 42
Upvotes: 2
Reputation: 44957
What one can also do is split up the implementing module into two traits:
given
s, which implements all the things and does the summon
inggiven
sHere is what it looks like:
package p {
trait SomeTypeclass[X]
object SomeTypeclass:
given intIsSomeTypeclass: SomeTypeclass[Int] with {}
trait ModuleIntf:
type X
given xIsSomeTypeclass: SomeTypeclass[X]
private[p] trait ModuleImplWithoutGivens extends ModuleIntf:
type X = Int
private[p] val summonedXIsSomeTypeclass: SomeTypeclass[X] = summon
trait ModuleImpl extends ModuleIntf with ModuleImplWithoutGivens:
given xIsSomeTypeclass: SomeTypeclass[X] = summonedXIsSomeTypeclass
}
import p.*
object CheckNoMissingMembers extends ModuleImpl
@main def itCompiles(): Unit =
val i: CheckNoMissingMembers.X = 42 // OK
println("It compiles, ship it.")
Upvotes: 0
Reputation: 51703
In Scala 2 you can use the trick with hiding implicit by name
// Scala 2
trait FooBar[X] {
def value: String
}
object FooBar {
implicit val intIsFooBar: FooBar[Int] = new FooBar[Int] {
override val value: String = "a"
}
}
trait Intf {
type A
implicit def aIsFoobar: FooBar[A]
}
object IntImpl extends Intf {
override type A = Int
override implicit val aIsFoobar: FooBar[A] = {
lazy val aIsFoobar = ???
implicitly[FooBar[A]]
}
}
println(IntImpl.aIsFoobar.value) // a
NullPointerException on implicit resolution
In Scala 3, what's the canonical method for pattern match that uses an erased type?
Is there a workaround for this format parameter in Scala?
Extending an object with a trait which needs implicit member
Constructing an overridable implicit (answer)
In Scala 3 this trick doesn't work any more.
In Scala 3 you can try to make the method inline and use scala.compiletime.summonInline
rather than the ordinary summon
// Scala 3
trait FooBar[X]:
def value: String
object FooBar:
given intIsFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "a"
trait Intf:
type A
/*inline*/ given aIsFoobar: FooBar[A]
object IntImpl extends Intf:
override type A = Int
override inline given aIsFoobar: FooBar[A] = summonInline[FooBar[A]]
println(IntImpl.aIsFoobar.value) // a
Overriding inline methods: https://docs.scala-lang.org/scala3/reference/metaprogramming/inline.html#rules-for-overriding
Please notice that with inlining we modified the method semantics. The implicit is resolved at the call site, not at the definition site
// Scala 2
trait FooBar[X] {
def value: String
}
object FooBar {
implicit val intIsFooBar: FooBar[Int] = new FooBar[Int] {
override val value: String = "a"
}
}
trait Intf {
type A
implicit def aIsFoobar: FooBar[A]
}
object IntImpl extends Intf {
override type A = Int
override implicit val aIsFoobar: FooBar[A] = {
lazy val aIsFoobar = ???
implicitly[FooBar[A]]
}
}
{
implicit val anotherIntFooBar: FooBar[Int] = new FooBar[Int] {
override val value: String = "b"
}
println(IntImpl.aIsFoobar.value) // a
}
// Scala 3
trait FooBar[X]:
def value: String
object FooBar:
given intIsFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "a"
trait Intf:
type A
/*inline*/ given aIsFoobar: FooBar[A]
object IntImpl extends Intf:
override type A = Int
override inline given aIsFoobar: FooBar[A] = summonInline[FooBar[A]]
{
given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "b"
println(IntImpl.aIsFoobar.value) // b
}
About the difference implicitly
vs. implicit
:
When doing implicit resolution with type parameters, why does val placement matter?
Why the Scala compiler can provide implicit outside of object, but cannot inside? (answer)
Setting abstract type based on typeclass
In Scala 2.13, why is it possible to summon unqualified TypeTag for abstract type?
In Scala 2 inlining can be achieved with Scala 2 macros.
Implicit Json Formatter for value classes in Scala
In https://docs.scala-lang.org/scala3/reference/contextual/relationship-implicits.html#abstract-implicits it's written
An abstract implicit val or def in Scala 2 can be expressed in Scala 3 using a regular abstract definition and an alias given. For instance, Scala 2's
implicit def symDecorator: SymDecorator
can be expressed in Scala 3 as
def symDecorator: SymDecorator given SymDecorator = symDecorator
You can ask how to override implicit in Scala 3 not changing the definition-site semantics. Probably, just resolving the implicit manually rather than using summon
// Scala 3
trait FooBar[X]:
def value: String
object FooBar:
given intIsFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "a"
trait Intf:
type A
def aIsFoobar: FooBar[A]
given FooBar[A] = aIsFoobar
object IntImpl extends Intf:
override type A = Int
override val aIsFoobar: FooBar[A] = FooBar.intIsFooBar
{
given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "b"
println(IntImpl.aIsFoobar.value) // a
}
More general but less conventional solution would be with Scala 3 macros + compiler internals
// Scala 3.2.1
import scala.quoted.{Quotes, Type, Expr, quotes}
import dotty.tools.dotc.typer.{Implicits => dottyImplicits}
import dotty.tools.dotc.core.Types.{Type => DottyType}
transparent inline def summonSecondBest[A]: A = ${summonSecondBestImpl[A]}
def summonSecondBestImpl[A: Type](using Quotes): Expr[A] =
import quotes.reflect.*
given c: dotty.tools.dotc.core.Contexts.Context =
quotes.asInstanceOf[scala.quoted.runtime.impl.QuotesImpl].ctx
val typer = c.typer
val search = new typer.ImplicitSearch(
TypeRepr.of[A].asInstanceOf[DottyType],
dotty.tools.dotc.ast.tpd.EmptyTree,
Position.ofMacroExpansion.asInstanceOf[dotty.tools.dotc.util.SourcePosition].span
)
val wildProtoMethod = classOf[typer.ImplicitSearch].getDeclaredField("wildProto")
wildProtoMethod.setAccessible(true)
val wildProto = wildProtoMethod.get(search).asInstanceOf[DottyType]
def eligible(contextual: Boolean): List[dottyImplicits.Candidate] =
if contextual then
if c.gadt.isNarrowing then
dotty.tools.dotc.core.Contexts.withoutMode(dotty.tools.dotc.core.Mode.ImplicitsEnabled) {
c.implicits.uncachedEligible(wildProto)
}
else c.implicits.eligible(wildProto)
else search.implicitScope(wildProto).eligible
def implicits(contextual: Boolean): List[dottyImplicits.SearchResult] =
eligible(contextual).map(search.tryImplicit(_, contextual))
val contextualImplicits = implicits(true)
val nonContextualImplicits = implicits(false)
val contextualSymbols = contextualImplicits.map(_.tree.symbol)
val filteredNonContextual = nonContextualImplicits.filterNot(sr => contextualSymbols.contains(sr.tree.symbol))
val successes = (contextualImplicits ++ filteredNonContextual).collect {
case success: dottyImplicits.SearchSuccess => success.tree.asInstanceOf[ImplicitSearchSuccess].tree
}
successes.tail.head.asExprOf[A]
// Scala 3
trait FooBar[X]:
def value: String
object FooBar:
given intIsFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "a"
trait Intf:
type A
def aIsFoobar: FooBar[A]
given FooBar[A] = aIsFoobar
object IntImpl extends Intf:
override type A = Int
override val aIsFoobar: FooBar[A] = summonSecondBest[FooBar[A]]
{
given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "b"
println(IntImpl.aIsFoobar.value) // a
}
Finding the second matching implicit
Or you can try to make A
a type parameter rather than type member
trait FooBar[X]
object FooBar:
given FooBar[Int] with {}
trait Intf[A: FooBar]
object IntImpl extends Intf[Int]
https://docs.scala-lang.org/scala3/reference/changed-features/implicit-resolution.html
Nesting is now taken into account for selecting an implicit. Consider for instance the following scenario:
def f(implicit i: C) = def g(implicit j: C) = implicitly[C]
This will now resolve the implicitly call to
j
, becausej
is nested more deeply thani
. Previously, this would have resulted in an ambiguity error. The previous possibility of an implicit search failure due to shadowing (where an implicit is hidden by a nested definition) no longer applies.
@AndreyTyukin's solution:
trait FooBar[X]:
def value: String
object FooBar:
given intIsFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "a"
trait Intf:
type A
def aIsFoobar: FooBar[A]
object implicits:
given FooBar[A] = aIsFoobar
object IntImpl extends Intf:
override type A = Int
override def aIsFoobar: FooBar[A] = summon[FooBar[Int]]
{
given anotherIntFooBar: FooBar[Int] = new FooBar[Int]:
override val value: String = "b"
println(IntImpl.aIsFoobar.value) // a
}
{
import IntImpl.implicits.given
println(summon[FooBar[Int]].value) // a
}
Upvotes: 3