I won’t give you the category-theory based explanation of monads. Instead, I’m going to show you the application of monads, but not on Ints and Strings, but on ordinary domain instances; instances that we persist using Hibernate. Finally, we’ll run the entire example in the Spring Framework.
Why the complexity, you ask? My team often found the simple examples understandable, but they struggled to see the application of the monads to day-to-day business code. “Sure, we write monads that read a String from a file, chain it to monad that converts the String to Int, but we’re having to generate reports,” they said.
The service
Let’s write a ReportService that generates reports and then produces a combined report by combining the individual reports into one. Its API is:
class ReportService {
def produce()
def combine()
}
Where the produce function generates the individual reports (pages, if you like); and the combine function takes the already generated reports and assembles the aggregate. We keep these reports in the database (and the content on the disk) and we map the RDBMS records to the Record instances. We are using Hibernate, leading the definition of the Report class:
@Entity
case class Report() {
@Id
@GeneratedValue
@BeanProperty
var id: Long = _
@Version
@BeanProperty
var version: Int = _
@BeanProperty
var fileName: String = _
}
As I said earlier, we are using Hibernate to manipulate the Record instances, so our ReportService will need to depend on the SessionFactory. Because we’re running in the Spring Framework, we can have the dependency injected in. We just have to give the appropriate metadata to the Spring Framework’s DI core:
@Service
class ReportService @Autowired() (private val sessionFactory: SessionFactory) {
def produce()
def combine()
}
We’ll start with the produce() function. The task is to generate [a fixed number of] reports. So, we’ll need a for loop whose every iteration creates a Report instance and saves it to the DB.
@Service
class ReportService @Autowired() (private val sessionFactory: SessionFactory) {
@Transactional
def produce() {
for (i < - 0 to 100) {
val report = Report()
val content = "Report %d".format(i)
report.fileName = "%d".format(i)
// save the report's content
sessionFactory.getCurrentSession.saveOrUpdate(report)
}
}
}
So far, so good. We have the fancy Scala for loop, but the code remains very similar to its Java equivalent. Now, let's deal with the IO. To save a report, we need to:
- create / open a file into which the content is going to go,
- produce the report's content,
- write the content to the file.
So, we have three operations. We go from Report to operation giving File, second that goes from operation giving File and operation giving Data to operation giving Unit. So, let's write these operations and define what we mean by Data:
@Service
class ReportService @Autowired() (private val sessionFactory: SessionFactory) {
type Data = Array[Char]
import scalaz._
import scalaz.effects._
def reportToFile(report: Report) = io {
new File("/Users/janmachacek/Tmp/r/%s".format(report.fileName))
}
def dataToFile(data: IO[Data])(file: File) = io {
println("Written " + new String(data.unsafePerformIO) + " to " + file)
}
...
}
The function reportToFile is the one that takes a Report and returns an operation that gives File. The operation that gives is whatever the io function returns. Then I have the dataToFile function that takes an operation that takes operation that gives Data and returns an operation that gives Unit. Figure 1 shows a picture of what we're trying to do.
Figure 1. The operations and the links between them

We would like to wire the three operations together to give us a new operation that combines the three together!
This is crucial concept: we're not just pushing data around, we're creating a new operation that combines the smaller operations to do something useful for us. So, let's drop in the real syntax:
@Service
class ReportService @Autowired() (private val sessionFactory: SessionFactory) {
type Data = Array[Char]
import scalaz._
import scalaz.effects._
def reportToFile(report: Report) = io {
new File("/Users/janmachacek/Tmp/r/%s".format(report.fileName))
}
def dataToFile(data: IO[Data])(file: File) = io {
println("Written " + new String(data.unsafePerformIO) + " to " + file)
}
@Transactional
def produce() {
for (i <- 0 to 100) {
val report = Report()
val content = "Report %d".format(i)
report.fileName = "%d".format(i)
(reportToFile(report) >>=
dataToFile(io {content.toCharArray})).unsafePerformIO
sessionFactory.getCurrentSession.saveOrUpdate(report)
}
}
}
Let's explore the mystical (reportToFile(report) >>= dataToFile(io {content.toCharArray})).unsafePerformIO line. We have reportToFile takes a Report and makes a thing that makes a File. Then we have dataToFile which takes a thing that makes Data and thing that makes File. We treat these things as black boxes--they do something obscure, unknown and unknowable. But these things have a "screen" on them; and we can look at that "screen" and obtain the current value.
And that's the principle of monads. They are opaque things that can return some value and they can be chained together to form new things that combine whatever the component things do.
And that explains the unsafePerformIO call. The (reportToFile(report) >>= dataToFile(io {content.toCharArray})) simply returns a new monad, but we must "look at its screen"; make it compute a value, in other words. And that's what the unsafePerformIO function does.
Onwards, then! Let’s implement the combine function that selects all existing reports, and merges them into a one fat report that it then saves.
@Service
class ReportService @Autowired() (private val sessionFactory: SessionFactory) {
type Data = Array[Char]
import scalaz._
import scalaz.effects._
import scala.collection.JavaConversions._
def reportToFile(report: Report) = io {
new File("/Users/janmachacek/Tmp/r/%s".format(report.fileName))
}
def dataToFile(data: IO[Data])(file: File) = io {
println("Written " + new String(data.unsafePerformIO) + " to " + file)
}
def fileToData(file: File) = io {
Source.fromFile(file).toArray
}
@Transactional
def produce() {
...
}
@Transactional
def combine() {
val reports =
sessionFactory.getCurrentSession.createCriteria(classOf[Report]).
list().asInstanceOf[java.util.List[Report]].toList
val out = Report()
out.fileName = "out.txt"
// take the reports and make one big fat report out
sessionFactory.getCurrentSession.saveOrUpdate(out)
}
}
The combine selects all Report records, and (using the function in scala.collection.JavaConversions._) converts the java.util.List[Report] into scala.collections.immutable.List[Report]. How do we now combine the reports? Let’s think about it:
- If the
reportscontains just one item, then the result is that one item, - If the
reportscontains two items, then we need some function that takes twoReports and makes the combinedReport, - If the
reportscontains many (i.e. more than two!) items, then we take the first two and combine them into one (using the function from the previous step). Then we take the combined report and combine it with the third item; then we take the combined report and combine it with the fourth item; and so on, until the end.
So, we need a function that combines two reports into one and then an operation that implements the algorithm we described earlier. The name for the operation is foldl1 and Figure 2 shows how it works:
Figure 2. foldl1 function

The arrows in the image are our function and report1, report2, ..., reportn is our list. Hence the code of the combine function becomes:
@Service
class ReportService @Autowired() (private val sessionFactory: SessionFactory) {
...
@Transactional
def combine() {
val reports =
sessionFactory.getCurrentSession.createCriteria(classOf[Report]).
list().asInstanceOf[java.util.List[Report]].toList
val out = Report()
out.fileName = "out.txt"
reports.foldl1 { (r1: Report, r2: Report) =>
// read the r1 and r2 from the files,
// combine them and write to out
out
}
sessionFactory.getCurrentSession.saveOrUpdate(out)
}
}
We now have to read the report from a file into Data and the combine the two Datas representing the reports together. The good news is that we have already written the monads that deal with the report IO. All we have to do is to combine them to form new interesting monad that combines the reports.
@Service
class ReportService @Autowired() (private val sessionFactory: SessionFactory) {
...
@Transactional
def combine() {
val reports =
sessionFactory.getCurrentSession.createCriteria(classOf[Report]).
list().asInstanceOf[java.util.List[Report]].toList
val out = Report()
out.fileName = "out.txt"
def combine(data: (Data, Data)) = io { data._1 ++ data._2 }
reports.foldl1 { (r1: Report, r2: Report) =>
(reportToFile(out) >>= dataToFile(
(reportToFile(r1) >>= fileToData) <|*|>
(reportToFile(r2) >>= fileToData) >>= combine)
).unsafePerformIO
out
}
sessionFactory.getCurrentSession.saveOrUpdate(out)
}
}
You already know about chaining the monads (using >>=), the new thing in the reports.foldl1 function is the <|*|> operation. Think of this operation indeed as multiplication, but multiplication of the computed values of the monads. The monad on the left-hand side ((reportToFile(r1) >>= fileToData)) computes Data, the monad on the right-hand side ((reportToFile(r2) >>= fileToData)) computes Data. In this case, we have Data * Data, in other words a pair–tuple in computer speak–of Data. And that’s precisely what the combine function takes (its body simply appends the two arrays) and it returns the combined IO of Data… and we can use it as input of the dataToFile function, which is an IO monad that follows the reportToFile(out) monads.
The whole contraption represents our report + report function; which we give to the foldl1 function. For completeness, we save the combined report using the injected sessionFactory.
Summary
This code combines dependency injection, load-time weaving (courtesy of the Spring Framework) with elegance and effectiveness of the Scala code. Finally, we take advantage of the IO monads in Scalaz to compose the individual trivial operations (report -> file, data -> file, etc.) to compose new operations that we use in the produce and combine functions.

