-
-
Save r-wheeler/62822302cb017f78da5c to your computer and use it in GitHub Desktop.
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
// Requires scalaz to be on the classpath | |
import scalaz._ | |
import scalaz.State._ | |
// The State monad was hard for me to understand because it | |
// seems as if you never actually store a value in the state and | |
// manipulate it inside of a method like you would in java. | |
// There are really two types of methods in the scalaz library | |
// around State. There is the State object, which contains | |
// in java terms, static methods, to create and manipulate | |
// state. Once you have as state, however, you can also | |
// call methods on that state object. Typically these methods | |
// create another state object in return. In some cases, | |
// the state's instance methods return a state and the new value. | |
// Once you attach a state to an object, you use that state | |
// to generate a new state. | |
// Conceptually, a State is a function that maps the current state | |
// to a new state and produces a value (derived from the old state). | |
// In scala, functions are first class objects so its possible to store | |
// a function (which is a value) in a variable. But more importantly, | |
// you can store a value in a new anonymous class. In scalaz, the | |
// function you provide is stored in an anonymous class call State. | |
// The name under which it is stored is "apply." This is convenient | |
// because we can than call that function by using function calling syntax e.g. | |
// "yourState(inputValue)." | |
// Since each State you create is really an anonymous class, when | |
// compose states together using some of the state methods explained later | |
// in this text, you are really composing functions together and | |
// building them up. They are not evaluated immediately because you are | |
// really performing function composition versus processing. Unlike java, | |
// you are performing function composition at runtime in a typesafe manner. | |
// To "run" the functions and return a value (either the state for its side effects | |
// or a value), you have to pass in an initial state or an initial value. | |
// Its important to recognize that you never get access to the target value | |
// in your wrapped function because the focus is on the state value. Why? | |
// Well if you needed the target value to calculate the new target value or | |
// the next state, then the target value is actually state that should have | |
// been in the state to begin with :-) The State monad is enforcing the separation | |
// of these 2 concepts. | |
// If you pass in the initial state, then the function you provided has to | |
// be able to compute a value and new state. Perhaps it will do this using external | |
// information or just the initial state itself. You can think | |
// of the initial state like an initial value, potentially but not always a blank | |
// or an empty structure, that you use for foldLeft. When using foldLeft you have | |
// to pass in an initial value to get the fold processing started with a well known | |
// initial condition. | |
// You are not limited to state information to compute your value. The | |
// function you provide can obtain values from anywhere to perform its | |
// computation. You can also ignore the value and only focus on transforming | |
// the state from one state to the next state based on external information | |
// or the previous state as well. In this case, you are using States purely | |
// for the side-effects. | |
// Our toy problem is to gather information from a variety of sources. The sources | |
// all produce information identified by a string and each named resource | |
// maps to another string. This is clearly a properties-like structure Map[String, String]. | |
// Each resource will provide this information using different data structures. | |
// Hence, our state will be a cache of these results stored in a map. We'll need | |
// to ensure that we can map from each source of information to our state. Some of | |
// the resources require information found in the cache and the sequence of access | |
// is important to ensure the information is available as needed. We are only | |
// interested in the state after, not the decorated value. In this formulation, | |
// the decorated value is Unit. | |
object Resource1 { | |
def getUrl(user: String) = Map("user" -> user, "url" -> "resource1://blah") | |
} | |
object Resource2 { | |
def getIt(url: String) = Map("resource1://blah" -> "resource2") | |
} | |
object Resource3 { | |
def getIt(): List[String] = List("resource3", "valueX") | |
} | |
// Here's our adapters to these resources. These methods return a state | |
// object. | |
def getResource1(user: String) = modify { s: Map[String, String] => s ++ Resource1.getUrl(user) } | |
def getResource2() = modify { s: Map[String, String] => | |
val url = s.getOrElse("url", "default://if-missing-url") | |
val value = Resource2.getIt(url) | |
s ++ value | |
} | |
// We will embed the resource conversion for Resource3 in the for-comprehension. | |
// Here's the for-comprehension that gets the information for a user. | |
def compute(user: String) = for { | |
_ <- getResource1(user) | |
_ <- getResource2() | |
_ <- modify { s: Map[String, String] => | |
val info = Resource3.getIt() | |
s ++ Map(info(0) -> info(1)) | |
} | |
} yield () | |
// We now "run" the the function. The return values is a tuple: (Map[String, String], Unit) | |
// so we just need to grab the state part which is _1. We could also have used .exec | |
// to just return the state. | |
println("Resources: " + compute("joe").run(Map[String, String]())._1) | |
/** Prints: | |
Resources: Map(user -> joe, url -> resource1://blah, resource1://blah -> resource2, resource3 -> valueX) | |
**/ | |
// The above is rather contrived problem. But we also see that there is no | |
// free lunch. Instead of having the state passed to each function, we are still | |
// having to code the translation of source-specific information to the state | |
// for storage in the state. That code never goes away and in fact, all we are really | |
// doing is moving the state handling code out of the Resource functions. That's | |
// a win because the resource functions do not have to be aware of the state | |
// as the processing is executed, however, that code still has to go somewhere | |
// and its messy. We could write some clever implicits but I think this makes | |
// the point. Also note that the state is injected into each function that is | |
// wrapped so one could view this as a very simple form of dependency injection. | |
// So in other words, many of the State examples on the web show much the code | |
// is cleaned up and indeed their code is much simpler. But the code cleanup | |
// occurs when the functions being called are already aligned with the structures | |
// used in the state (in the above case, the Map[String, String]). The State monad | |
// really feels like a fancy foldLeft function but instead of operating on a sequence | |
// of values, you are operating on a sequence of functions with a variable combiner | |
// function between each of the elements. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment