00:00:15.299
Cool! Hey everybody, can everyone hear me? Good, sounds good. All right, thanks for coming down, y'all. I truly appreciate it.
00:00:20.950
Before we get started, I want to say a big, massive, heartfelt thanks to RubyConf, all the organizers and volunteers. They put this event together; it's a ton of work, and I'm truly thankful.
00:00:27.279
On a personal note, I'd also like to say thank you to The Iron Yard, where I work, for supporting me, and thank you to all of you for being here today.
00:00:38.019
You had a choice in where to be right now, and you're here with me, so I appreciate it. Today we’re going to talk about composition.
00:00:45.129
I typically dislike talks that start with 'Webster defines composition as...' but I'm going to do it anyway. Here's why: I enjoy grounding my talks with a background.
00:00:53.290
I looked through Wikipedia, and it struck me as amusing that the first definitions for object and functional composition are strikingly similar. Well, not exactly the same, but isomorphic. This resonated with me.
00:01:05.350
So, I’m going to define composition for myself. To me, composition means combining pieces cohesively. It’s what we do as programmers all the time. We analyze complicated problems, chip away at them, find solutions for the smaller components, and then reassemble everything into a cohesive whole.
00:01:18.250
That's the assembly process I'm interested in understanding more deeply. My goals for this talk are to explain what object and functional composition are.
00:01:31.360
I’ll spend some time on functional composition, assuming that most people here might be less familiar with it. I also aim to intentionally confuse the two a little. Often, this object-oriented versus functional debate is treated as an either/or scenario, as if one approach is correct while the other is wrong.
00:01:44.079
But I believe they are more similar than different. It's like the parable of the blind men and the elephant; we're just interpreting the same fundamental concept through different lenses, and I want to explore that notion without any lens jokes here. I also want to introduce some terminology related to functional programming for those who might want to explore that world further.
00:02:09.459
Before we dive in, let me share some background about myself. I have been working with Ruby for six or seven years now, but my interest in composing small components goes back much further.
00:02:22.090
I majored in math, went to grad school, and wrote my thesis on applications of monads to topology. Don’t worry if you don’t know what those terms mean; it’s not crucial for today’s discussion. The point is, I have spent a lot of time contemplating category theory.
00:02:51.550
To summarize my journey: it began with Legos, then I explored topology, moved on to category theory, and ultimately discovered Haskell. That's the lens through which I’ll be presenting everything today.
00:03:03.730
To borrow a couple phrases, I would say that I’m 'Haskell infected' to my functional core. How many people here have looked at Haskell in a cursory way?
00:03:25.210
Okay, that's quite a few. How many of you found it somewhat daunting? Me too! I have a degree in category theory, and it’s not an easy subject. If you've glanced at Haskell, you might have encountered constructs like 'Oh, it's easy!' But let me explain to you what a monad is.
00:03:38.650
I appreciate that kind of explanation, but it can be overwhelming. The reason I got into Haskell was my interest in category theory and the need for a job after graduation—people told me it was useful. But you don’t need to dive into that complexity to understand the day-to-day work you’re doing.
00:03:56.830
It’s great to learn more, but the only essential concept I’ll mention is what a category is. In mathematical terms, a category consists of a collection of objects, functions between those objects, and the means to compose them.
00:04:17.620
Category theory, therefore, studies composition. This is the genesis of my interest in these topics. I have a lot more complicated terms and definitions to discuss; don't worry, I'm not going to dive into those right now—but do feel free to come and discuss them with me later.
00:04:41.300
Now, let's get back to composition. Okay, so let's talk about object composition. I’ll assume this is somewhat familiar to you. Maybe I'll give a less-than-perfect definition, but feel free to argue with me later if you'd like.
00:04:52.580
Object composition refers to one object maintaining a reference to another and employing that reference to perform its duties. You may have heard the expression to favor object composition over class inheritance somewhere. Usually, it's discussed in relation to inheritance as a 'has-a' relationship rather than an 'is-a' relationship. It's not about specialization; it's about composition.
00:05:32.000
Let me make this more concrete by using an example. I'd like to model a system that represents topologists. One important thing I want my topologist to do is tell jokes. So, what is a topologist? Well, it's naturally a noun, which means I probably want a class for it.
00:06:03.760
How do I represent a topologist? They need a method that allows them to tell jokes. Let’s assume they remember all the jokes they've heard throughout their lives. So, we initialize our topologist, and perhaps we can pretend the list of topologist jokes is just a static array. This is straightforward.
00:06:27.650
So, here we go. We initialize a working object, and we can ask it for a joke. Here’s an example of a joke—just for reference, here's an image of a Klein bottle; it doesn't have an inside. Now that we've established this functionality, the important thing is that we want to understand how our system can extend further.
00:06:53.340
Now let's say we want to model Algebraic Topology in particular. Algebraic topologists have a particular tendency to not care much about things outside of group theory. Here’s where I try to model an Algebraic Topologist.
00:07:12.200
An algebraic topology is clearly a type of topologist; it's right there in the name—this implies it's a subclass. They would still have their same collection of jokes, but they pick only the ones they actually care about. It works well and progresses nicely.
00:07:31.580
Next, let's say we have a Loud Topologist. This class is a simple example that picks their joke but delivers it by shouting everything loudly all the time. Again, this works fine—no problem so far. But now we run into the classic diamond problem.
00:08:09.590
What happens when we want to create a Loud Algebraic Topologist? We have specialized our classes in two separate directions, but we don't have a good way to recombine them without creating a complexity.
00:08:20.510
This leads to what I call a dilemma. We want to avoid unnecessary complexity while developing a solution based on composition. So I’m introducing an alternative approach. Let's take my topologist class, just as I have it. Instead of specializing, we could create an Algebraic Topologist, which no longer needs to manage its own jokes.
00:09:05.720
Now, the Algebraic Topologist just needs to hold a reference to a topologist friend. When they want to tell a joke, they’ll ask their topologist friend for a joke they know, pick one they like, and deliver it. This illustrates the 'has-a' relationship nicely.
00:09:33.320
This strategy of creating different layers of objects is beneficial. For example, a Loudspeaker could hold a reference to a Jokester. As long as the Jokester knows how to tell a joke, the Loudspeaker can modify the joke output. This approach works beautifully.
00:09:46.970
Using this object-oriented flexibility, we can configure various behaviors in our system. There are certainly some downsides to this approach, however; assumptions could lead to brittle designs.
00:10:05.330
We can run into problems if we assume things about our objects that aren’t true based on context.
00:10:11.780
Using explicit design patterns can help manage these challenges, but when discussing functional programming, issues like this do not typically arise. In functional programming, there's often just one way to do things, and it tends to work.
00:10:48.990
Let’s shift gears and explore function composition and how it works. Warning: we’re going to touch upon some high school math here, but I'll make it quick! Think of functions as mathematical constructs; for instance, when you input a value, a function may output a result by performing an operation. The exciting part is we can compose these functions together.
00:11:38.860
The operation for composition has a mathematical notation, and if we were to write this in Haskell, it would look similar to how we typically handle math functions, which is really nice. This composition operator in Haskell, represented as a dot (.), is not just for convenience; it provides a way to build more complex functionality from simpler functions.
00:12:10.210
To illustrate this, imagine our web server as a function that receives a request and eventually returns a response after some I/O operations. Middleware functions take existing applications and wrap them in additional functionality.
00:12:45.100
We're looking at our middleware chain, where each middleware function is another layer that takes in some input, applies some transformations or operations, and returns modified output. If you've ever worked with Rack middleware, this concept will be quite familiar.
00:13:32.170
The beauty of this abstraction lies in its simplicity and elegance; we can compose our systems smoothly without needless complexity. In functional programming, objects should be pure, which means they take in data and return data without side effects.
00:14:09.830
With that understanding, let's reevaluate our topologist modeling problem with a functional perspective. Often, the most challenging aspect is identifying the kinds of functions you're trying to create.
00:14:34.150
For our web server, we would have a particular function taking requests. Therefore, a topologist's function is one that processes input and returns output, refining our understanding of domain responsibilities. The essence of functional programming is to see objects more as verbs or actions rather than static nouns.
00:15:34.150
We can express a Topologist function that takes a collection of jokes and yields a joke rather than focusing on representing an object with associated attributes. This approach encourages thinking in terms of transformations or compositions.
00:16:01.560
For clarity, I’d like to introduce another concept: point-free notation, which allows us to remove direct references to values. It’s about composing operations without explicitly mentioning values.
00:16:39.800
So as we define our functions, we can create a pipeline that executes the defined logic smoothly. Similarly, the Algebraic Topologist would maintain a pipeline that filters, shuffles, and delivers jokes based on preferences.
00:17:16.760
With this functional composition, it becomes clear how we can unify behaviors to create adaptable and flexible systems. It is possible to parametrize the creation of our Topologist with their sense of humor and delivery style.
00:17:30.350
So far, we’ve established a foundation where a Topologist is a wider construct that adapts based on criteria, allowing greater extensibility. We can use this design to create an ecosystem of types and behaviors without the complexity of deep class hierarchies.
00:18:07.560
In our Ruby application, this means preserving the object-oriented principles while composing functions at a higher level, allowing for a more Ruby-esque style of programming. Objects can embody behaviors that are easily chained, resulting in cleaner, more comprehensible code.
00:18:39.970
Using methods like 'width', we maintain the ability to produce variations of our Topologist with customizable properties while still leveraging functions and procs for behavior.
00:19:08.350
This approach offers flexibility, and we’re able to implement features like allowing the Loud Topologist to receive and produce behaviors without losing the essence of the core components.
00:19:44.590
As we mold our functions, we can identify what methods to include in our definitions for effective composition. It's about clarifying responsibilities and exploiting the power of functional programming to design systems that thrive on simplicity and modularity.
00:20:18.360
Continuing this thread, we can observe that thinking functionally empowers us to consider types as latent properties; they govern how we compose our behaviors without necessarily adhering to strict class structures.
00:20:54.350
For example, when we compose our functions in Ruby, we can apply the same principles of chaining, helping us focus on the flow and transformations of data rather than getting bogged down in state management.
00:21:45.880
As we reflect on the example of an ordered pipeline, keep in mind, it’s critical to make intermediary steps explicit. By mapping our data through each transformation distinctly, we establish clearer pathways for function composition.
00:22:28.290
Given our previous focus on parsing and building orders, it’s beneficial to define clear interfaces between operations, which allow us to extract and test individual steps without convoluted dependencies.
00:23:01.300
With defined steps, extracting functionality becomes trivial and enhances maintainability.
00:23:32.870
This assertion leads us to reconsider how we structure our services and what shared interfaces we can create.
00:23:45.850
As we examine how functions fail, variability in data input shapes our responses. In functional programming, we want to propagate values and errors cautiously.
00:24:21.760
Thus, when building our functions, we strive for systems where failures do not cascade unexpectedly; instead, they become explicit parts of our definition process.
00:24:58.110
By reflecting this in our systems, we develop functionality that maintains clarity while adapting flexibly to changes.
00:25:28.810
In conclusion, our main goal is to construct pathways that assure we handle types effectively, allowing for reuse and composition throughout.
00:26:02.200
Remember, our goal is to define pure functions that maintain composability while not losing sight of the simplicity and elegance behind individual components.
00:26:41.370
By utilizing abstractions prudently, you can equip your systems with the power of functionality and flexibility.
00:27:17.870
Thank you for your time, and I hope you found these insights valuable. If you’re interested in getting further into this topic, feel free to connect with me later. Happy coding!