Skip to content

Instantly share code, notes, and snippets.

@fare
Created December 22, 2016 03:43
Show Gist options
  • Save fare/7620f8fd2708feedcec3bc5d8863eb9c to your computer and use it in GitHub Desktop.
Save fare/7620f8fd2708feedcec3bc5d8863eb9c to your computer and use it in GitHub Desktop.
Context passing in Scala
trait IsContext {
def contextualContent: List[HasContext[this.type]] = List()
contextualContent.map { item => item.setContext(this) }
}
trait HasContext[+Context] {
private var ctx: Any = null
def setContext(context: Any) {
assert(ctx == null)
ctx = context
}
lazy val context = ctx.asInstanceOf[Context]
}
trait Contextual[+Context] extends IsContext with HasContext[Context]
@fare
Copy link
Author

fare commented Dec 22, 2016

The same code, somewhat cleaned up. Should be a package, but was made an object for the sake of http://www.tryscala.com/

object context { // should be package

  trait IsContext {
    protected def contextualItems: List[HasContext[IsContext]] = List()
    contextualItems.map { item => item.setContext(this) }
  }
  trait HasContext[+Context <: IsContext] {
    private var ctx: Any = null
    private[context] def setContext(context: Any) {
      assert(ctx == null)
      ctx = context
    }
    lazy val context = ctx.asInstanceOf[Context]
  }
  trait Contextual[+Context <: IsContext] extends IsContext with HasContext[Context]


  // Let's use it
  abstract class TopLevel extends IsContext
  abstract class MidLevel extends Contextual[TopLevel]
  abstract class LowLevel extends HasContext[MidLevel]

  trait UserTopLevel extends TopLevel with IsContext {
    def userId : Int
    def userMidLevel : UserMidLevel
    override def contextualItems = userMidLevel :: super.contextualItems
  }
  trait UserMidLevel extends MidLevel with Contextual[UserTopLevel] {
    def userId = context.userId
  }
}

@fare
Copy link
Author

fare commented Dec 22, 2016

Same old, moving the parameter away from HasContext, where it's not actually helping with static typechecking.

object context { // should be package

  trait IsContext {
    protected def contextualItems: List[HasContext] = List()
    contextualItems.map { item => item.setContext(this) }
  }
  trait HasContext {
    type Context <: IsContext
    private var ctx: Any = null
    private[context] def setContext(context: Any) {
      assert(ctx == null)
      ctx = context
    }
    lazy val context = ctx.asInstanceOf[Context]
  }
  trait Contextual extends IsContext with HasContext


  // Let's use it (should be in another package)
  abstract class TopLevel extends IsContext
  abstract class MidLevel extends Contextual { type Context <: TopLevel }
  abstract class LowLevel extends HasContext { type Context <: MidLevel }

  trait UserTopLevel extends TopLevel {
    def userId : Int
    def userMidLevel : UserMidLevel
    override def contextualItems = userMidLevel :: super.contextualItems
  }
  trait UserMidLevel extends MidLevel with Contextual {
    type Context <: UserTopLevel
    def userId = context.userId
  }
}

@fare
Copy link
Author

fare commented Dec 24, 2016

Better, just pass along values in mutual recursive definitions:

object simpleContext {

  trait HasContext[+Context] {
    has =>
    def context: Context
    trait ThisContext extends HasContext[Context] {
      def context = has.context
    }
  }
  trait IsContext[+Context] extends HasContext[Context] {
    this: Context =>
    override def context: this.type = this
  }

  // Using it...
  trait Page {
    def show () : Unit
  }

  trait User {
    val name : String
  }
  trait UserContext extends IsContext[UserContext] {
    val user : User
    val mainPage : Page
  }
  trait HelloPage extends Page with HasContext[UserContext] {
    def show () = println("Hello, " + context.user.name)
  }
  val ctx1 = new UserContext {
    val user = new User { val name = "Alice" }
    val mainPage = new HelloPage with ThisContext
  }

  // Refining it...
  trait Player extends User {
    var points : Int
  }
  trait PlayerContext extends IsContext[PlayerContext] with UserContext {
    val user: Player
  }
  trait ScorePage extends HelloPage with HasContext[PlayerContext] {
    override def show () = {
      super.show ()
      println("You have " + context.user.points + " points")
    }
  }
  val ctx2 = new PlayerContext {
    ctx =>
    val user = new Player {
      val name = "Bob"
      var points = 99
    }
    val mainPage = new ScorePage with ThisContext
  }
  def main(args: Array[String]): Unit = {
    ctx1.mainPage.show()
    ctx2.mainPage.show()
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment