Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Scalaz Features for Everyday Usage Part 3: State Monad, Writer Monad, and Lenses

DZone's Guide to

Scalaz Features for Everyday Usage Part 3: State Monad, Writer Monad, and Lenses

In Part 3 of this series Monads and Lenses are introduced with code samples, with a focus on stuff that is practical to use.

· Java Zone
Free Resource

Managing a MongoDB deployment? Take a load off and live migrate to MongoDB Atlas, the official automated service, with little to no downtime.

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

We'll start with one of the additional monads provided by Scalaz.

Image title


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 for instance, we could use a writer to keep track of the operations we've executed to get to some specific value.

So let's 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 don't just get the result of the operation, but also the aggregated values collected by the Writer. So when we write

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 becomes 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 example of this can be found here.

State Monad

Another interesting monad is the State monad, which provides a convenient 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.

Let's 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 and great when you need to pass some state around a set of functions.

Lenses

So enough with the monads for now, let's look at Lenses. With Lenses it is possible to easily (well easier than just copying case classes by hand) change values in nested object hierarchies. Lenses can do many 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 lenses 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)))

Conclusion

There are still two subjects I want to write about, and those 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, don't really fall in the category of everyday usage, so I'll spend a couple of other articles on that in the future.

MongoDB Atlas is the easiest way to run the fastest-growing database for modern applications — no installation, setup, or configuration required. Easily live migrate an existing workload or start with 512MB of storage for free.

Topics:
scalaz ,monads ,lenses ,scala ,functional programming

Published at DZone with permission of Jos Dirksen, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}