Passa il parametro implicito attraverso più oggetti

Oct 08 2020

Mi chiedo se sia possibile passare parametri impliciti attraverso singleton come quello

case class Greet(g: String)

object Foo {
  def greet(name: String)(implicit greet: Greet = Greet("Hello")) = println(greet.g + " " + name)
}

object Bar {
  def greetBar = Foo.greet("Bar")
}


object Main {
  def main(args: Array[String]): Unit = {
    implicit val greet: Greet = Greet("Goodbye")
    
    Foo.greet("Sunshine") // Goodbye Sunshine
    Bar.greetBar // Hello Bar
  }
}

Bar.greetBarnon è influenzato dal valore implicito in main, ma voglio che sia influenzato senza passare il parametro implicito a greetBar, quindi c'è un modo per fare qualcosa del genere? Forse c'è un modo per impostare un oggetto implicito ma al di fuori di esso?

Risposte

3 DmytroMitin Oct 08 2020 at 17:59

È necessario aggiungere un parametro implicito al metodo

object Bar {
  def greetBar(implicit greet: Greet /*= Greet("Hello")*/) = Foo.greet("Bar")
}

implicit val greet: Greet = Greet("Goodbye")
Bar.greetBar // Goodbye Bar

oppure trasforma l'oggetto in una classe e aggiungi un parametro implicito alla classe

class Bar(implicit greet: Greet /*= Greet("Hello")*/) {
  def greetBar = Foo.greet("Bar")
}

implicit val greet: Greet = Greet("Goodbye")
(new Bar).greetBar // Goodbye Bar

Ho commentato il valore predefinito /*= Greet("Hello")*/. Se non vuoi greetBarcompilare quando non c'è uno scopo implicito, dovresti tenerlo escluso. Se vuoi un comportamento simile a greet(cioè Greet("Hello")quando non c'è un ambito implicito) allora dovresti rimuoverlo.

Tieni presente che puoi evitare di ripetere il valore predefinito se definisci una priorità inferiore implicita nell'oggetto associato

case class Greet(g: String)
object Greet {
  implicit val lowPriorityGreet: Greet = Greet("Hello")
}

object Foo {
  def greet(name: String)(implicit greet: Greet) = println(greet.g + " " + name)
}

object Bar {
  def greetBar(implicit greet: Greet) = Foo.greet("Bar")
}
// class Bar(implicit greet: Greet) {
//   def greetBar = Foo.greet("Bar")
// }

implicit val greet: Greet = Greet("Goodbye")

Foo.greet("Sunshine") // Goodbye Sunshine
Bar.greetBar // Goodbye Bar
// (new Bar).greetBar // Goodbye Bar

Vedi anche Come racchiudere un metodo che ha impliciti con un altro metodo in Scala?

Voglio farlo per impostare Greetimplict per tutti i metodi inBar

In linea di principio, puoi farlo con un'annotazione macro (ma non dovresti)

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

@compileTimeOnly("enable macro annotations")
class greetAware extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro GreetAwareMacro.impl
}

object GreetAwareMacro {
  def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
    import c.universe._

    val greet = TermName(c.freshName("greet"))
    val implicitGreet = q"""implicit val $greet: Greet = Greet("Hello")""" def isImplicit(param: Tree): Boolean = param match { case q"$mods val $_: $_ = $_" => mods.hasFlag(Flag.IMPLICIT) } annottees match { case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" :: Nil => val body1 = body.map { case q"$mods def $tname[..$tparams](...$paramss): $tpt = $expr" => val paramss1 = if (paramss.nonEmpty && paramss.last.nonEmpty && isImplicit(paramss.last.head)) paramss.init :+ (paramss.last :+ implicitGreet) else paramss :+ List(implicitGreet) q"$mods def $tname[..$tparams](...$paramss1): $tpt = $expr" case notMethod => notMethod } q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body1 }"
    }
  }
}

Utilizzo:

@greetAware
object Foo {
  def greet(name: String) = println(implicitly[Greet].g + " " + name)
}

@greetAware
object Bar {
  def greetBar = Foo.greet("Bar")
  def xxx(i: Int) = ???
  def yyy(i: Int)(implicit s: String) = ???
}

implicit val greet: Greet = Greet("Goodbye")

Foo.greet("Sunshine") // Goodbye Sunshine
Bar.greetBar // Goodbye Bar

//scalac: object Foo extends scala.AnyRef {
//  def <init>() = {
//    super.<init>();
//    ()
//  };
//  def greet(name: String)(implicit greet$macro$1: Greet = Greet("Hello")) = println(implicitly[Greet].g.$plus(" ").$plus(name)) //} //scalac: object Bar extends scala.AnyRef { // def <init>() = { // super.<init>(); // () // }; // def greetBar(implicit greet$macro$2: Greet = Greet("Hello")) = Foo.greet("Bar"); // def xxx(i: Int)(implicit greet$macro$2: Greet = Greet("Hello")) = $qmark$qmark$qmark;
//  def yyy(i: Int)(implicit s: String, greet$macro$2: Greet = Greet("Hello")) = $qmark$qmark$qmark
//}