Design Patterns

Summarized using AI

Composition

James Dabbs • November 10, 2016 • Cincinnati, OH

The video titled 'Composition' features a talk by James Dabbs at RubyConf 2016, focusing on the theme of composition in programming. Dabbs discusses how programmers decompose complex problems and recompose solutions effectively, stressing the importance of understanding both object and functional composition. He aims to highlight the similarities and differences between these two approaches, emphasizing that they should not be viewed in isolation.

Key points discussed in the talk include:
- Definition of Composition: Dabbs defines composition as the process of combining pieces cohesively to solve programming problems.
- Object Composition: He explains object composition as a 'has-a' relationship, where one object maintains a reference to another, aiding in functionality instead of relying solely on inheritance.
- Real-World Examples: Using the metaphor of topologists, he models objects and demonstrates the complexities of inheritance through humor by illustrating how jokes can be delivered by different types of topologists (e.g., Loud Topologist).
- Functional Composition: Dabbs shifts focus to functional composition, explaining it as combining functions to build complex functionality from simpler operations. He uses Haskell syntax to illustrate this approach, emphasizing clear input-output relationships without side effects.
- Middleware as Composition: He explains middleware functions in web servers, where each function can process input and produce output, demonstrating the elegance and simplicity of functional composition.
- Point-Free Notation: Introduced as a way to compose functions without naming values, enhancing modularity and clarity in coding.
- Reflections on Object and Functional Paradigms: Dabbs concludes that functional programming principles can enhance object-oriented designs, fostering simplicity and flexibility in programming.

In conclusion, the talk encourages programmers to embrace both object-oriented and functional approaches harmoniously to achieve better design and development practices. Dabbs emphasizes simplicity in constructing systems, allowing for clear paths of functionality and adaptability.

Overall, the insights from this talk invite programmers to rethink their approach to composition in their coding practices, aiming for elegant and effective solutions.

Composition
James Dabbs • November 10, 2016 • Cincinnati, OH

RubyConf 2016 - Composition by James Dabbs

Our work as programmers consists largely of problem decomposition and solution recomposition. This talk is interested in how we cobble small units together into cohesive solutions. We'll examine and compare both object and functional composition, using a Haskell-inspired, functional style of Ruby. Along the way, we'll see how good functional principles can improve our object-oriented design, and vice versa.

RubyConf 2016

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!
Explore all talks recorded at RubyConf 2016
+82