3
votes

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")
3
Not sure whether this help or not, but you can declare constraint on where the trait can be declared, like: trait Tax { this: Trade => (from: debasishg.blogspot.hu/2010/02/…)Gábor Bakos
Thanks for the suggestion. I experimented with self-types and chose to extend 'MicroServices' directly rather than constrain the eventual type to do so. It might be a better starting point, but I think both approaches lead to the compiler needing an explicit definition of 'actorSystem' in the eventual type ('MicroServicesPrimaryStage'). I think it's interesting that the members of the outer object are not considered members of the class that's mixed together inside the outer object's scope.richj
I found this answer that explains the difference and perhaps the self-type more clearly expresses the intention: stackoverflow.com/questions/7250374/…richj
Self-contained code is helpful (or a build.sbt because I'm lazy), and updating the question to reflect the chatter in comments is helpful.som-snytt

3 Answers

0
votes

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?

0
votes

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.

0
votes

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.)