In the previous chapter, we discussed the purely functional style with the help of essential libraries such as cats. This library performs quite well on tasks of purely functional programming, but in practice, that is not quite enough for comfortable programming.
If you take a look at conventional imperative languages such as Java, you will see that they usually have a lot of libraries and infrastructure for performing specific tasks. Moreover, it is also possible to argue that the choice of programming language is primarily driven by the infrastructure it provides.
This way, for example, Python is a de facto standard for machine learning, because it provides an elaborate set of scientific libraries to perform scientific computing, and R is a de facto standard for statistical computing. Companies often choose Scala because it provides access to Spark and Akka libraries for machine learning and distributed computing.
Hence, when talking about a particular programming style, it is of great importance to also mention that it is an infrastructure that is developed around the staff. In this chapter, we will cover this infrastructure by looking at a bunch of other libraries that exist for purely functional programming in Scala with cats.
The following topics will be covered in this chapter:
- The Cats effect
- Server-side programming
We will start this chapter by looking at the concurrency library for cats.
The Cats effect is a library for concurrent programming in cats. Its main feature is a bunch of type classes, data types, and concurrency primitives to describe concurrent programming in Scala with cats.
The concurrency primitives support among other things:
- Resource managementâthink try-with-resources.
- Seamless composition of parallel computations.
- Communication between parallel computations.
We will start discussing the library by looking at its central concurrency primitive, IO, and some capabilities of Cats that we will need in the process of discussing it.
Before diving deep into the library and discussing its features, we need to mention a particular operator that is frequently used throughout this library. We have already discussed the Applicative type class, and that it is useful for parallel composition.
An operator from this type class that is frequently used in cats is a so-called right product operator.
The operator in question takes two computations, performs a product between them, and takes only the right-hand result. Particularly in the Cats effect, the operator is frequently used to specify that one event should happen after another.
It also has a symbolic form, which looks like this: *>.
The primary data type that the Cats effect offers is IO. This is a data type that defines a computation that is to be performed at some point in the future. For example, you can have the following expression:
object HelloWorld extends App {
val hello = IO { println("Hello") }
val world = IO { println("World") }
(hello *> world).unsafeRunSync
}
Crucial detail to notice about IO is that it is precisely a description of the computation. Here, cats supports a so-called computation as a value paradigm. Computation as a value dictates that you should not evaluate your competition straight away, but you should store the descriptions of these computations. This way, you will be able to evaluate them at any point in the future.
This approach has a number of benefits, and this is what we are going to discuss next.
The first benefit Cats has is referential transparency. In the preceding example, the computation to print hello world to the command line will not be evaluated right away. It is side effecting, and the fact that we do not evaluate it right away means it is referentially transparent. You can evaluate the computation as follows:
(hello *> world).unsafeRunSync
IO has a bunch of methods, the names of which are prepended with the unsafe word.
Unsafe methods are generally what their prefix says, unsafe. This means that they may block, produce side effects, throw exceptions, and do other things that may cause you a headache. Following the description of the IO type in the documentation itself, you should only call such a method once, ideally at the end of your program.
So, basically, the main idea is that you describe your entire program in terms of the IO primitive, using the conveniences provided by this primitive by the Cats effect library. Once your entire application is described, you can run the application.
Since a computation expressed in terms of IO is not executed immediately but is merely stored as a description of a computation, it is possible to execute the computation against different execution strategies. For example, you may want to run the computation against various concurrent backends, each with its own concurrency strategies. You may want to run a competition synchronously or asynchronously. Later in this chapter, we will see how exactly this is done.
The central domain of the application of the Cats effect is asynchronous programming. Asynchronous programming is an event-driven style of programming, where you do not waste threads and other resources on blocking, waiting for some event to happen.
Consider, for example, that you have a web server that handles incoming HTTP requests. It has a pool of threads that are used by the server to handle each request. Now, the handlers themselves may require some blocking operations. For example, contacting a database for contacting an external HTTP API can be a potentially blocking operation. This is because the database or an HTTP API does not respond immediately as a rule. This means that if a request handler needs to contact such a resource, it will need to wait for the service to reply.
If such waiting is done naively, by blocking an entire thread and reviving it once the request is available, we have a situation where we waste threads. If such a server comes under a high load, there is a danger that all of the threads will be blocked for the majority of the time. Blocking means that they do not do anything and are just waiting for a response from a resource. Since they are not doing anything, these threads could have well been used to handle other requests that possibly do not require such kinds of blocking.
Precisely for this reason, current server-side programming is aimed toward asynchronous processing, which means that if a handler needs to contact some potentially blocking resource, it contacts it. However, once it has nothing else to do, it is supposed to release its thread. It will continue the computation once the response it is waiting for is available.
This k...