简体   繁体   中英

How to implicitly inject a value from an outer scope into a Scala trait

I'm trying to define a reusable trait that expects a value to be in an outer scope. I can define the trait inside the outer scope and it will work, but won't be reusable. When I move the trait to a separate scope, the trait can't access the value and I haven't found a way to declare it as present in the outer scope of the type the trait is being mixed in to.

The closest I have got so far is this:

import javafx.beans.property.ObjectProperty

import akka.actor.{Props, ActorSystem}

import javafx.event.EventHandler
import javafx.stage.{WindowEvent => JWindowEvent}

import scalafx.application.{Platform, JFXApp}
import scalafx.scene.Scene
import scalafx.scene.canvas.Canvas
import scalafx.scene.paint.Color


object MicroServicesApp extends JFXApp {
  implicit val system = ActorSystem("system")

  val canvas = new Canvas {
    width = 1200
    height = 900
  }

  stage = new MicroServicesPrimaryStage with AutomaticMicroServicesWindowCloser {
    title.value = "Map Viewer"

    scene = new Scene {
      fill = Color.LightGreen

      content = canvas
    }
  }
}

class MicroServicesPrimaryStage(implicit val actorSystem: ActorSystem) extends JFXApp.PrimaryStage with MicroServices {
}

/**
 * A class enabled with a micro-services actor system.
 */
trait MicroServices {
  def actorSystem: ActorSystem
}

/**
 * An automatic window closer for a ScalaFX and Akka micro-services application.
 *
 * When this trait is mixed in to a class with the MicroServices trait and the onCloseRequest property,
 * the onCloseRequest property will be initialized with a useful default event handler that shuts down
 * the Akka actor system as well as the ScalaFX platform.
 */
trait AutomaticMicroServicesWindowCloser extends MicroServicesWindowCloser {
  def onCloseRequest: ObjectProperty[EventHandler[JWindowEvent]]

  def onCloseRequest_=(handler: EventHandler[JWindowEvent]): Unit

  onCloseRequest = closeRequest()
}

/**
 * A window closer for a ScalaFX and Akka micro-services application.
 */
trait MicroServicesWindowCloser extends MicroServices {
  def closeRequest(): EventHandler[JWindowEvent] = new EventHandler[JWindowEvent] {
    override def handle(e: JWindowEvent)
    {
      println("... closing application.")

      actorSystem.shutdown()
      Platform.exit()
    }
  }
}

It gets pretty close to what I'm after, the only give-away is the need for the client code to declare the value in the outer scope to be implicit. Ideally I would like the client code to mix-in the trait without changing anything else.

In the example I can use 'system' from within 'MicroServicesPrimaryStage', but not from within the mixed-in trait. I think this is because 'system' is in scope but not considered to be defined as a member of 'MicroServicesPrimaryStage'.

I could create an alias for 'system' with a val or a def and make it work that way, but that also means an extra step in modifying the client code. It would be nice if the trait could require a definition for 'system' and be able to find it in the outer scope at the point where the trait is mixed-in.

Is this possible?

Edit 1

These two println statements illustrate the cause of my confusion:

stage = new MicroServicesPrimaryStage with AutomaticMicroServicesWindowCloser {
  println(s"val system is accessible from outer scope: $system ...")                        // compiles
  println(s"... but is not mixed-in to MicroServicesPrimaryStage as ${this.system}.")     // does not compile
  ...

I don't think the cake pattern can solve this on its own, because the question is about how the type system interacts with definitions in outer scopes.

Edit 2

SBT file for use with Java 8:

name := "workspace-sbt"

version := "1.0"

scalaVersion := "2.11.4"

resolvers += Opts.resolver.sonatypeSnapshots

libraryDependencies ++= Seq("org.scalatest"     %  "scalatest_2.11" % "2.2.1" % "test",
                            "org.scalafx"       %% "scalafx"        % "8.0.20-R7-SNAPSHOT",
                            "com.typesafe.akka" %% "akka-actor"     % "2.3.7")

You are mistaken:

"In the example I can use 'system' from within 'MicroServicesPrimaryStage', but not from within the mixed-in trait. I think this is because 'system' is in scope but not considered to be defined as a member of 'MicroServicesPrimaryStage'."

This is not true. You can certainly use super class members as definitions for abstract members of mixed in traits. Consider this:

trait Foo { 
    def foo: String 
    def printFoo = println(foo)
}

class FooBar(val foo)

object FooBar {
    def main(argv: Array[String]) = new FooBar("foo") with Foo printFoo
}

This compiles and will print "foo" if run. Is it not what you are trying to do?

Sorry if I, too, am missing something.

This is just the classic charlotte cake pattern.

Or, maybe you're asking for a fruit cake, with an extra surprise in the next layer. (Maybe a King's cake is a better metaphor.)

package cakesample

// something useful
trait Something {
  def thing: String
}

// a trait requiring something
trait Needy { _: Something =>
  def theThingIs: String = thing
}

// another trait that uses something
trait User { _: Something =>
  def use: String = thing * 2
}

// fruit cake fixings
case class Widget(w: String)

trait WidgetFramework {
  // used by the framework
  def widget: Widget

  trait WidgetCog {
    def run() = Console println s"Running ${widget.w}"
  }
}

// sample usage
object Test extends App with Something with Needy with User with WidgetFramework {
  // normal cake, a charlotte
  def thing = "hello, world"
  Console println s"$theThingIs: $use"

  // a fruit cake

  // define a widget
  val widget = Widget("my widget")

  // to be used by an object implementing a trait
  object client extends WidgetCog

  client.run()
}

I don't know why it should be yellow, except yellow is funnier than pound in this context. (Update: charlotte is more technically correct; but in the spirit of the season, a fruit cake is maybe what you're after.)

Maybe that's what you're looking for:

scala> abstract class Aaaa(implicit val a: Int)
defined class Aaaa

scala> class Kkk extends Aaaa
<console>:9: error: could not find implicit value for parameter a: Int
       class Kkk extends Aaaa
                         ^

scala> implicit val a = 5
a: Int = 5

scala> class Kkk extends Aaaa
defined class Kkk

scala> new Kkk
res12: Kkk = Kkk@1a79ef3

scala> res12.a
res13: Int = 5

Let's imagine, that Int is an ActorSystem )

This value will be accessible from both Kkk and Aaaa . But implicit value should be defined in the scope, where you actually mixin Aaaa .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM