3

I would like to implement a generic implicit for a class that can be overriden. As following:

tooling.scala

package tooling    

case class Tool( msg: String)

class Toolkit{
  def usingTool(source:String)(implicit tool:Tool){
    println( s"[$source] using tool: ${tool.msg}" )
  }
}

user.scala

package user

class User{
  val toolkit = new Toolkit()

  def usingGenericTool(){
    toolkit.usingTool( "from generic usage")
  }

  def usingSpecificTool(){
    implicit lazy val tool = Tool("shovel")
    toolkit.usingTool( "from generic usage")
  }
}

If I define a generic implicit Tool implementation in a companion object or trait, I get the warning that tool and the generic implicit are ambiguous implicit values.

How can I define a generic implicit val that still can be overridden in the scope of a method and reused in various classes?

dstibbe
  • 1,589
  • 18
  • 33

3 Answers3

1

You can just add implicit parameters as if they were ordinary ones, i.e.:

toolkit.usingTool("from generic usage")(Tool("shovel"))

Also each implicit member is a priority — implicit that are defined in ancestor classes overrides implicits defined in parent classes, even with different name.

So the following example will print 2:

trait LowPriorityImplicit {
  implicit val x = 1
}

object Test extends LowPriorityImplicit {
  implicit val y = 2

  def f(implicit i: Int) = println(i)

  f // => 2
}
Maxim
  • 1,209
  • 15
  • 28
  • I don't want to, since I'll be calling that method several times – dstibbe Apr 09 '16 at 15:10
  • As far as I know, there is no priority mechanism for implicits defined in same class. For implicits in class hierarchy — I updated my answer. – Maxim Apr 09 '16 at 15:12
  • But if I define the implicit in the Test trait, as you showed in your example, I CAN'T define an implicit in the method body of the Test class. – dstibbe Apr 09 '16 at 15:39
  • That's what I said in a comment above. – Maxim Apr 09 '16 at 15:48
  • But they aren't defined in the same class... One in the trait and the other in a method of a class extending that trot. Also, in your example you are overriding the trait implicit in the object implicit. So why not in its method? – dstibbe Apr 09 '16 at 15:57
1

Define the default implicit in the companion object of Tool:

case class Tool( msg: String)
object Tool {
  implicit val defaultTool: Tool = Tool("Generic")
}

And you can override it by importing other tools, or creating an implicit Tool variable:

object Tools {
  implicit val Special: Tool = Tool("special")
}

def usingTools(): Unit = {
  val toolkit = new Toolkit()
  toolkit.usingTool("using the default")

  { // importing an override
    import Tools.Special
    toolkit.usingTool("using imported")
  }

  { // creating an override
    implicit val anotherTool: Tool = Tool("local")
    toolkit.usingTool("using local implicit val")

    // using a Tool other then the currently imported as an exception:
    toolkit.usingTool("overriding once manually")(Tool("manual"))
  }
}
Kolmar
  • 14,086
  • 1
  • 22
  • 25
0

If you have access to the code of Tool you can define default implicit in the companion object of Tool (not companion of Toolkit or User). Local implicit "overrides" implicit in companion.

case class Tool(msg: String)
object Tool {
  implicit lazy val tool = Tool("default")
}

u.usingGenericTool() //[from generic usage] using tool: default
u.usingSpecificTool() // [from generic usage] using tool: shovel

Suppose you don't have access to the code of Tool. Indeed, if you define the default implicit locally or in a parent of User you'll have error ambiguous implicit values. You can hide implicit by name

class User {
  val toolkit = new Toolkit()
  implicit lazy val tool = Tool("default")

  def usingGenericTool() {
    toolkit.usingTool("from generic usage")
  }

  def usingSpecificTool() {
    implicit lazy val tool = Tool("shovel") // the same name!
    toolkit.usingTool("from generic usage")
  }
}

val u = new user.User
u.usingGenericTool() //[from generic usage] using tool: default
u.usingSpecificTool() // [from generic usage] using tool: shovel

NullPointerException on implicit resolution

Caching the circe implicitly resolved Encoder/Decoder instances

How can an implicit be unimported from the Scala repl?

Scala implicit def do not work if the def name is toString

Is there a workaround for this format parameter in Scala?

Another option is shapeless.LowPriority (names of implicits can be different)

// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
class User {
  val toolkit = new Toolkit()
  implicit def tool0(implicit lp: LowPriority) = Tool("default")

  def usingGenericTool() {
    toolkit.usingTool("from generic usage")
  }

  def usingSpecificTool() {
    implicit lazy val tool = Tool("shovel")
    toolkit.usingTool("from generic usage")
  }
}

u.usingGenericTool() //[from generic usage] using tool: default
u.usingSpecificTool() // [from generic usage] using tool: shovel

Implicit parameter precedence

General way of ensuring implicit definition always has higher/lower priority

If you have access to the code of Toolkit you can use default parameter

class Toolkit {
  def usingTool(source: String)(implicit tool: Tool = Tool("default")) {
    println(s"[$source] using tool: ${tool.msg}")
  }
}

u.usingGenericTool() //[from generic usage] using tool: default
u.usingSpecificTool() // [from generic usage] using tool: shovel

In Scala 3 there is scala.compiletime.summonFrom

def usingGenericTool() = {
  inline given Tool = summonFrom {
    case given Tool => summon[Tool]
    case _ => Tool("default")
  }
  toolkit.usingTool("from generic usage")
}

u.usingGenericTool() //[from generic usage] using tool: default
u.usingSpecificTool() // [from generic usage] using tool: shovel

https://docs.scala-lang.org/scala3/reference/metaprogramming/compiletime-ops.html#summoning-implicits-selectively

In Scala 2 we can achieve this behavior with a macro

def usingGenericTool(): Unit = {
  implicit lazy val tool0: Tool = summonOrElse(Tool("default"))
  toolkit.usingTool("from generic usage")
}

u.usingGenericTool() //[from generic usage] using tool: default
u.usingSpecificTool() // [from generic usage] using tool: shovel

// in a different subproject
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

def summonOrElse[A](default: A): A = macro summonOrElseImpl[A]

def summonOrElseImpl[A: c.WeakTypeTag](c: blackbox.Context)(default: c.Tree): c.Tree = {
  import c.universe._

  c.typecheck(q"""
    val ${c.internal.enclosingOwner.name.toTermName} = null
    _root_.scala.Predef.implicitly[${weakTypeOf[A]}]
  """, silent = true) match {
    case EmptyTree => default
    case t => t
  }
}

See also

Finding the second matching implicit

create an ambiguous low priority implicit

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66