Constructors, Reducers, and Converters
Explore the CRC pattern—constructors, reducers, and converters—in Elixir and Phoenix. Understand how Phoenix processes web requests by breaking them into small functions that transform connection data. Gain insight into Plug and how reducers manipulate web request state to build responses step by step.
We'll cover the following...
Pipelines and functional composition play a big role in Elixir. One pattern called CRC plays a huge role in many different Elixir modules. Its roots are closely entwined with the common function Enum.reduce/3. Let’s take a closer look.
Web frameworks in functional languages all use variations of a common pattern. They use data represented by a common data type and use many tiny, focused functions to change that data, step by step. For example, in the JavaScript world, the state reducer pattern by Kent Dodds uses many of the same strategies. Clojure has a similar framework called Ring.
In Phoenix, the Plug framework follows the same pattern. Let’s explore this pattern in more detail.
Plug Patterns
In Elixir, many modules are associated with a core type. The String module deals with strings, Enum deals with enumerables, and so on. As often as possible, experienced Elixir developers strive to make a module’s public functions related to its core type. Constructors create a term of the core type from convenient inputs. Reducers transform a term of the core type to another term of that type. Converters convert the core type to some other type. Taken together, we’ll call this pattern CRC.
So far, CRC might seem abstract, so let’s take a simple, tangible example.
We built a module that has one of each of these functions. Test it by firing up the IEx in the terminal below:
Notice that this tiny module works with integers, and has three kinds of functions. All of them deal with integers as an input argument, an output, or both. The new/1 function is a constructor, and it’s used to create a term of the module’s type from a String input. The to_string/1 function is a converter that takes an integer input and produces an output of some other type, a String in our case. The add/2 reducer takes an integer as both the input and output.
Let’s put it to use in two different ways. First, let’s use the reduce/3 function with our three functions in the terminal below like this:
And, we should see something like this:
We take a list full of integers and a string that we feed into our constructor that produces an integer we can use with our reducer. Since Enum.reduce/3 takes the accumulator as the second argument, we build a reducer/2 function that flips the first two arguments around. Then, we call Enum.reduce/3, and pipe that result into the converter.
It turns out that the same kinds of functions that work in reducers also work in pipes, like this:
Test these functions in the terminal below and we should see something like this:
Perfect! The backslash at the end of each line tells IEx to delay execution because we have more to do. The functions in this Number module show an example of CRC, but it’s not the only one. This pattern is great for taking something complicated, like the response to a complex request, and breaking it down into many small steps. It also lets us build tiny functions that each focus on one thing.
CRC in Phoenix
Phoenix processes requests with the CRC pattern. The central type of many Phoenix modules is a connection struct defined by the Plug.Conn module. The connection represents a web request. We can then break down a response into a bunch of smaller reducers that each process a tiny part of the request, followed by a short converter.
Here’s what the program looks like:
We can see CRC in play. Phoenix itself serves as the constructor. It builds a common piece of data that has both request data and response data. Initially, the requested data is populated with information about the request, but the response data is empty. Then, Phoenix developers build a response, piece by piece, with small reducers. Finally, Phoenix converts the connection to a response with the render/1 converter.
Let’s make this example just a little more concrete. Say we wanted to have our web server build a response to some request, piece by piece. We might have some code that looks like the code given below:
We can test this by running in the IEx terminal below and we should see something like this:
Notice the two main concepts at play. First is the common data structure, the connection. The second is a function that takes an argument, called acc for the accumulator, that we’ll use for our connection and two arguments. Our function is called a reducer because we can reduce an accumulator and a few arguments into a single accumulator.
Now, with our fictional program, we can string together a narrative that represents a web request. For our request, we make the connection, and then we pass that connection through two reducers to set the status to 200 and the body to :ok. After we’ve built a map in this way, we can then give it back to our web server by passing it to our render/1 converter to send the correct body with the correct status down to the client.
The Plug.Conn common data structure
The Plug is a framework for building web programs, one function at a time. Plugs are either Elixir functions or tiny modules that support a small function named call. Each function makes one little change to a connection—the Plug.Conn data structure. A web server simply lets developers easily string many such plugs together to define the various policies and flows that make up an application.
We don’t have to guess what’s inside. We can see it for ourselves. Type iex -S mix to launch interactive Elixir in the context of our Phoenix application. Key in an empty %Plug.Conn{} struct and hit enter. We should see these default values:
The response status is the standardized HTTP status.
So that’s the “common data structure” piece of the equation. Next, we’ll look at the reducer.
Reducers in Plug
Now, we’ve seen Plug.Conn, the data that stitches Phoenix programs together. We don’t need to know too much to understand many of the files that make up a Phoenix application beyond three main concepts:
- Plugs are reducer functions.
- They take a
Plug.Connstruct as the first argument. - They return a
Plug.Connstruct.
When we see the Phoenix configuration code, it’s often full of plugs. When we see lists of plugs, imagine a pipe operator between them. For example, when we see something like this:
We can mentally translate that code to this:
Now, with that background, we’re going to look at the heart of our Phoenix infrastructure, and even if we have only a small amount of experience with Phoenix, we’ll be able to understand it. Keep in mind that this information will come in handy because it will help us understand exactly what happens when a LiveView runs.