Last active
May 9, 2018 05:47
-
-
Save retronym/1395578 to your computer and use it in GitHub Desktop.
Styles of config propagation: Manual, Implicits, DynamicVariable, Reader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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