Skip to content

Instantly share code, notes, and snippets.

@retronym
Last active May 9, 2018 05:47
Show Gist options
  • Save retronym/1395578 to your computer and use it in GitHub Desktop.
Save retronym/1395578 to your computer and use it in GitHub Desktop.
Styles of config propagation: Manual, Implicits, DynamicVariable, Reader
package scalaz.example
object Reader extends App {
/**
* Manual propagation of the environment (in the example, `contextRoot`.)
*/
object Config0 {
def fragment1(contextRoot: String) = <a href={contextRoot + "/foo"}>foo</a>
def fragment2(contextRoot: String) = <a href={contextRoot + "/bar"}>bar</a>
def html(contextRoot: String) = <html>
<body>
{fragment1(contextRoot)}{fragment2(contextRoot)}
</body>
</html>
def render = html("/c1")
}
/**
* Implicit parameter propagation of the environment (in the example, `contextRoot`.)
* - Advantage: less boilerplate than above
* - Disadvantage: hard to reason that the *same* context root is used everywhere, as some part of the
* computation could pass a different one. (This might also be an advantage, depending on
* your requirements)
*/
object Config0_1 {
case class ContextRoot(s: String) { def toString = s }
def fragment1(implicit contextRoot: ContextRoot) = <a href={contextRoot + "/foo"}>foo</a>
def fragment2(implicit contextRoot: ContextRoot) = <a href={contextRoot + "/bar"}>bar</a>
def html(implicit contextRoot: ContextRoot) = <html>
<body>
{fragment1}{fragment2}
</body>
</html>
def render = html(ContextRoot("/c1"))
}
/**
* Uses a inheritable thread local, provided by `scala.util.DynamicVariable`, to avoid
* explicitly passing the state.
*
* Advantages: no clutter in the code; values can be set separately on on different threads.
* Disadvantages: The functions are no longer pure. `html` is not reusable.
* While the value is available on child threads, it is *not*
* available on threads from an pool that are working on your
* behalf (for example, by perform `xs.par.map(f)`)
*
* @see http://www.youtube.com/watch?v=8oiN-hzBKHE
* @see http://stackoverflow.com/questions/5116352/when-we-should-use-scala-util-dynamicvariable
*/
object Config1 {
import scala.util.DynamicVariable
val contextRoot = new DynamicVariable[String]("")
def fragment1 = <a href={contextRoot.value + "/foo"}>foo</a>
def fragment2 = <a href={contextRoot.value + "/bar"}>bar</a>
def html = <html>
<body>
{fragment1}{fragment2}
</body>
</html>
def render = contextRoot.withValue("/c1") {
html
}
}
/**
* Use the Reader monad to thread the environment through the call tree.
*
* Functions that require the environment are of type `ConfigReader`
*
* @see https://groups.google.com/d/msg/scala-user/O8udPVbdg_c/mIMgMnJPomQJ
*/
object Config2 {
/**
* Reader Monad. Computation of a value of type `A`, given an environment of type `E`.
*
* Think of the environment as read-only, configuration.
*/
case class Reader[E, A](run: E => A) {
def map[B](f: A => B) = Reader[E, B](s => f(run(s)))
def flatMap[B](f: A => Reader[E, B]) = Reader[E, B](s => f(run(s)).run(s))
}
implicit def CR[E, A](run: E => A) = Reader(run)
// convenience type alias.
type StringReader[A] = Reader[String, A]
import xml._
def fragment1: StringReader[Elem] = (contextRoot: String) => <a href={contextRoot + "/foo"}>foo</a>
def fragment2: StringReader[Elem] = (contextRoot: String) => <a href={contextRoot + "/bar"}>bar</a>
def html1(f1: Elem, f2: Elem) = <html><body>{f1}{f2}</body></html>
def html: StringReader[Elem] = for {
f1 <- fragment1
f2 <- fragment2
} yield html1(f1, f2)
def render = html.run("/c1")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment