Scalaz features for everyday usage part 3: State Monad, Writer Monad and lenses
In this article in the mini series on Scalaz, we'll look at a couple of additional monads and patterns available in Scalaz. Once again, we'll look at stuff that is practical to use, and avoid the inner details or Scalaz. To be more precise, in this article we'll look at:
- Writer monad: Keep track of a sort of logging during a set of operations
- State monad: Have an easy way of tracking state across a set of computations
- Lenses: Easily access deeply nested attributes and make copying case classes more convenient
The following articles are currently available in this series:
- Scalaz features for everyday usage part 1: Typeclasses and Scala extensions
- Scalaz features for everyday usage part 2: Monad Transformers and the Reader Monad
- Scalaz features for everyday usage part 3: State Monad, Writer Monad and lenses
We'll start with one of the additional monads provided by Scalaz.
Writer monad
Basically each writer has a log and a return value. This way you can just write your clean code, and at a later point determine what you want to do with the logging (e.g validate it in a test, output it to the console, or to some log file). So we could use a writer, for instance, to keep track of the operations we've executed to get to some specific value.
So lets look at the code and see how this thing works:
import scalaz._
import Scalaz._
object WriterSample extends App {
// the left side can be any monoid. E.g something which support
// concatenation and has an empty function: e.g. String, List, Set etc.
type Result[T] = Writer[List[String], T]
def doSomeAction() : Result[Int] = {
// do the calculation to get a specific result
val res = 10
// create a writer by using set
res.set(List(s"Doing some action and returning res"))
}
def doingAnotherAction(b: Int) : Result[Int] = {
// do the calculation to get a specific result
val res = b * 2
// create a writer by using set
res.set(List(s"Doing another action and multiplying $b with 2"))
}
def andTheFinalAction(b: Int) : Result[String] = {
val res = s"bb:$b:bb"
// create a writer by using set
res.set(List(s"Final action is setting $b to a string"))
}
// returns a tuple (List, Int)
println(doSomeAction().run)
val combined = for {
a <- doSomeAction()
b <- doingAnotherAction(a)
c <- andTheFinalAction(b)
} yield c
// Returns a tuple: (List, String)
println(combined.run)
}
In this sample we've got three operations that do something. In this case, they don't really do that much, but that doesn't matter. The main thing is, that instead of returning a value we return a Writer (note that we could have also created the writer in the for comprehension), by using the set function. When we call run on a Writer, we not just get the result of the operation, but also the aggregated values collected by the Writer. So when we do:
type Result[T] = Writer[List[String], T]
def doSomeAction() : Result[Int] = {
// do the calculation to get a specific result
val res = 10
// create a writer by using set
res.set(List(s"Doing some action and returning res"))
}
println(doSomeAction().run)
The result looks like this: (List(Doing some action and returning res),10). Not that exciting, but it comes more interesting when we start using the writers in a for-comprehension.
val combined = for {
a <- doSomeAction()
b <- doingAnotherAction(a)
c <- andTheFinalAction(b)
} yield c
// Returns a tuple: (List, String)
println(combined.run)
When you look at the output from this you'll see something like:
(List(Doing some action and returning res,
Doing another action and multiplying 10 with 2,
Final action is setting 20 to a string)
,bb:20:bb)
As you can see we've gathered up all the different log messages in a List[String] and the resulting tuple also contains the final calculated value.
When you don't want to add the Writer instantiation in your functions you can also just create the writers in a for-comprehension like so:
val combined2 = for {
a <- doSomeAction1() set(" Executing Action 1 ") // A String is a monoid too
b <- doSomeAction2(a) set(" Executing Action 2 ")
c <- doSomeAction2(b) set(" Executing Action 3 ")
// c <- WriterT.writer("bla", doSomeAction2(b)) // alternative construction
} yield c
println(combined2.run)
The result of this sample is this:
( Executing Action 1 Executing Action 2 Executing Action 3 ,5)
Cool right? For this sample we've only shown the basic Writer stuff, where the type is just a simple type. You can of course also create Writer instances from more complex types. An examples of this can be found here: http://stackoverflow.com/questions/35362240/creating-a-writertf-w-a-from-a-writerw-a
The state monad
Another interesting monad, is the State monad. The state monad provides a convient way to handle state that needs to be passed through a set of functions. You might need to keep track of results, need to pass some context around a set of functions, or require some (im)mutable context for another reason. With the (Reader monad) we already saw how you could inject some context into a function. That context, however, wasn't changeable. With the state monad, we're provided with a nice pattern we can use to pass a mutable context around in a safe and pure manner.
Lets look at some examples:
case class LeftOver(size: Int)
/** A state transition, representing a function `S => (S, A)`. */
type Result[A] = State[LeftOver, A]
def getFromState(a: Int): Result[Int] = {
// do all kinds of computations
State[LeftOver, Int] {
// just return the amount of stuff we got from the state
// and return the new state
case x => (LeftOver(x.size - a), a)
}
}
def addToState(a: Int): Result[Int] = {
// do all kinds of computations
State[LeftOver, Int] {
// just return the amount of stuff we added to the state
// and return the new state
case x => (LeftOver(x.size + a), a)
}
}
val res: Result[Int] = for {
_ <- addToState(20)
_ <- getFromState(5)
_ <- getFromState(5)
a <- getFromState(5)
currentState <- get[LeftOver] // get the state at this moment
manualState <- put[LeftOver](LeftOver(9000)) // set the state to some new value
b <- getFromState(10) // and continue with the new state
} yield {
println(s"currenState: $currentState")
a
}
// we start with state 10, and after processing we're left with 5
// without having to pass state around using implicits or something else
println(res(LeftOver(10)))
As you can see, in each function we get the current context, make some changes to it, and return a tuple consisting of the new state, and the value of the function. This way each function has access to the State, can return a new one, and returns this new state, together with the function's value as a Tuple. When we run the above code we see the following:
currenState: LeftOver(15)
(LeftOver(8990),5)
As you can see each of the functions does something with the state. With the get[S] function we can get the value of the state at the current moment, and in this example we print that out. Besides using the get function, we can also set the state directly using the put function.
As you can see, a very nice, and simple to use pattern, but great when you need to pass some state around a set of functions.
Lenses
So enough with the monads for now, lets look at Lenses. With Lenses it is possible to easily (well more easy than just copying case classes by hand) change values in nested object hierarchies. Lenses can do a whole lot of things, but in this article I'll introduce just some basic features. First, the code:
import scalaz._
import Scalaz._
object LensesSample extends App {
// crappy case model, lack of creativity
case class Account(userName: String, person: Person)
case class Person(firstName: String, lastName: String, address: List[Address], gender: Gender)
case class Gender(gender: String)
case class Address(street: String, number: Int, postalCode: PostalCode)
case class PostalCode(numberPart: Int, textPart: String)
val acc1 = Account("user123", Person("Jos", "Dirksen",
List(Address("Street", 1, PostalCode(12,"ABC")),
Address("Another", 2, PostalCode(21,"CDE"))),
Gender("male")))
val acc2 = Account("user345", Person("Brigitte", "Rampelt",
List(Address("Blaat", 31, PostalCode(67,"DEF")),
Address("Foo", 12, PostalCode(45,"GHI"))),
Gender("female")))
// when you now want to change something, say change the gender (just because we can) we need to start copying stuff
val acc1Copy = acc1.copy(
person = acc1.person.copy(
gender = Gender("something")
)
)
In this sample we defined a couple of case classes, and want to change a single value. For case classes this means, that we have to start nesting a set of copy operations to correctly change one of the nested values. While this can be done for simple hierarchies, it quickly becomes cumbersome. With lensen you're offered a mechanism to do this in a composable way:
val genderLens = Lens.lensu[Account, Gender](
(account, gender) => account.copy(person = account.person.copy(gender = gender)),
(account) => account.person.gender
)
// and with a lens we can now directly get the gender
val updated = genderLens.set(acc1, Gender("Blaat"))
println(updated)
#Output: Account(user123,Person(Jos,Dirksen,List(Address(Street,1,PostalCode(12,ABC)),
Address(Another,2,PostalCode(21,CDE))),Gender(Blaat)))
So we define a Lens, which can change a specific value in the hierarchy. With this lens we can now directly get or set a value in a nested hierarchy. We can also create a lens which modifies a value and returns the modified object in one go by using the =>= operator.
// we can use our base lens to create a modify lens
val toBlaBlaLens = genderLens =>= (_ => Gender("blabla"))
println(toBlaBlaLens(acc1))
# Output: Account(user123,Person(Jos,Dirksen,List(Address(Street,1,PostalCode(12,ABC)),
Address(Another,2,PostalCode(21,CDE))),Gender(blabla)))
val existingGender = genderLens.get(acc1)
println(existingGender)
# Output: Gender(male)
And we can use the >=> and the <=< operators to combine lenses together. For example in the following code sample, we create to separate lenses which are then combined and executed:
// First create a lens that returns a person
val personLens = Lens.lensu[Account, Person](
(account, person) => account.copy(person = person),
(account) => account.person
)
// get the person lastname
val lastNameLens = Lens.lensu[Person, String](
(person, lastName) => person.copy(lastName = lastName),
(person) => person.lastName
)
// Get the person, then get the lastname, and then set the lastname to
// new lastname
val combined = (personLens >=> lastNameLens) =>= (_ => "New LastName")
println(combined(acc1))
# Output: Account(user123,Person(Jos,New LastName,List(Address(Street,1,PostalCode(12,ABC)),
Address(Another,2,PostalCode(21,CDE))),Gender(male)))
Conclusions
There are still two subjects I want to write about, and that are Validations and Free monads. In the next article in this series I'll show how you can use ValidationNEL for validations. Free Monads however, I think, doesn't really fall in the category of everyday usage, so I'll spent a couple of other articles on that in the future.