Skip to content

Instantly share code, notes, and snippets.

@aappddeevv
Last active December 22, 2015 22:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save aappddeevv/6543896 to your computer and use it in GitHub Desktop.
Save aappddeevv/6543896 to your computer and use it in GitHub Desktop.
There's no free lunch when using the State monad
// 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