Before we can talk about optics proper, we need to lay a bit of groundwork. We’ll need to understand what Algebraic Data Types (ADTs) are. Luckily, we can explain that as we build our motivating example.
The first data type we’ll be working with throughout this article is Classifier
, in the sense of a machine learning classifier. If you reduce that to its simplest possible expression (and possibly slightly further), you get:
case class Classifier(
name : String,
classCount: Int
)
A Classifier
is composed of two things: a name, used to identify it, and a class count, which you use to make sense of the probability distribution it yields. A String
and an Int
.
That and keyword is extremely important: it’s the defining feature of a product type. An aggregation of values, using and as the aggregating operator.
I’ll be using the following diagram to represent product types:
Where:
Classifier
) represents the product type itself.String
and Int
) are the types of these fields.The trapezium shape is supposed to look like ⋀
, the and mathematical operator.
In our example, we’re accessing classifiers through an HTTP API, which is protected by authentication. We currently support two authentication mechanisms:
Here’s the Scala code that corresponds to this:
sealed trait Auth
case class Token(
token: String
) extends Auth
case class Login(
user : String,
password: String
) extends Auth
Auth
is flagged as sealed
, which means that the only two possible implementations are Token
and Login
: an Auth
is either a Token
or Login
.
Note the or keyword: that’s the defining feature of a sum type. It’s an aggregation of values, using or as the aggregating operator.
I’ll be using the following diagram to represent sum types:
Where:
Auth
) represents the sum type itself.Auth
might be a Token
, but doesn’t have to be.Login
and Token
) are the types of these possible values.The trapezium shape is supposed to look like ∨
, the or mathematical operator.
Finally, we need to bring Classifier
and Auth
together - we need some type to aggregate both, so that we can have all the information needed to connect to a remote service in a single value.
We’ll need a Classifier
and an Auth
. We’ve seen that before, it’s a product type:
case class MlService(
auth : Auth,
classifier: Classifier
)
Note how this is a product type composed of a product type and a sum type. This is generally what we mean when we talk about ADTs: nested product and sum types.
MlService
can be represented with the following graph:
We’ll be spending a significant part of this article trying to find shortcuts through that diagram.
While writing the data structure we’ll be using throughout the article, we have defined the following:
With that knowledge in hand, we can now start talking about optics. The first one we’ll concern ourselves with is lens.