Do you want to create robust and composable abstractions? Here’s an iterative process to define the essence of a domain and build composability into the core and then demonstrates how to apply this process to the Processing graphics library to develop a composable vector graphics system.
SlidesBuilding Composable Abstractions OSCON 2018 - download
I teach functional programming and a lot of people ask me this question. How do I structure a functional program, how do I structure software?
They read about functional programming, they learn the basics, they learn recursion, they learn immutable data structures that kind of thing, and they’re like, “I can program Fibonacci just fine, but how do I structure a whole program that has to get stuff done?”
Unfortunately, the functional programming literature is not as developed as say the object or any programming literature, so there’s not a lot of material out there to read. I’ve been thinking a lot about this and I came up with a process.
A beginner needs a process that steps them through each little bit. Even if it’s not the most efficient process or the way they would do it as an expert, it’s something to get them started. That’s what I’m going to present here today.
My name is Eric Normand. This talk is called “Building Composable Abstractions.”
Just a bit about myself, I’ve been programming in functional languages since 2001. I started with Common Lisp. Now I’m into Clojure and I write about that at lipcast.com. You can find more about me there.
Motivating abstractions. This is Aristotle. About 2,300 years ago, he developed a system of physics, Aristotelian physics. It was his attempt at explaining how things worked in the world. This is an excerpt of it. It had a lot of concept. It was a big, big tome of a work. I’ll just explain some of these concepts here.
Ideal speed, that’s the idea of that big things move slower than small things. Natural place. That is the idea that if you move a rock to the top of the hill, it’ll tend to roll down to its natural place which is at the bottom of the hill.
There’s natural motion, which is the motion, like when I’m walking, it’s smooth and graceful and not hurting myself, but imagine if I got hit by a car like my limbs will go flying and that’s unnatural motion. I might hurt myself the way I’m moving, it’s unnatural motion.
As you know modern people, we look at this and were like, “That doesn’t work too well. I can already see bugs in it. There’s corner cases, there’s big things to move fast, like what are you talking about?”
But this was the dominant form of physics in the western world for 2,000 years. What physicists would do was write more books trying to explain away the bugs. So they’re writing all these conditionals like, “Well some big things move fast but that’s it because of this.” They just spend a book explaining that.
So, basically it grew by getting worse [laughs] over time. It’s just like more and more you had to learn to be able to do physics, and it never solved the fundamental problems.
About 300 years ago, Isaac Newton came along and did a big re write. He threw out Aristotle and came up with a new system. This is what it looked like three nice elegant laws. This is not an excerpt. This is it. These are Newton’s laws of motion. I’ll go over them quickly.
Inertia is the idea that without any forces acting on a thing, it’s not going to change like it’s not going to move in a different way. It’s just going to keep going in a straight line or not move at all.
Acceleration, this is the sum of the forces is equal to the mass times the acceleration. And then, action reaction says that if I do a push, that actually creates two forces in the model, one in the direction of the push and one in the opposite direction, and they’re the same strength of force.
You see, they’re very nice, concise laws that are written mathematically and there’s only four concepts here that you’ll see in those laws. He went from this mishmash of concepts with no coherence between them, to a very small, concise, elegant thing.
Of course, this does not explain everything. There’s nothing about liquids or there’s nothing about air resistance and stuff like that. It doesn’t explain everything but you can add it on top without invalidating this. You don’t have to say, “If it’s a liquid, it’s going to act this other way.”
It still acts in that same way, but there’s more stuff that you have to know about the liquid. Physicists nowadays, add on to this without breaking the core, without having to make exceptions in the core.
What do I want to say? There’s no way to translate between Aristotle and Newton. You had to do a big rewrite. You had to start over, throw it out.
A lot of times, we talk about I’m going to get it working, and then I’ll clean it up later. That’s what Aristotle did. He got the rock rolls to the bottom of the hill. He got that working. When you clean it up, it just turns into this one little concept that doesn’t cohere with the rest of the stuff. The process of solve a user story. Solve a user story. Solve a user story, then clean up. That does not lead to a nice, clean, coherent design in the end. You might have clean code but you don’t have a nice abstraction in there.
On to the process. These are the objectives I had when I was developing the process. The first one is I wanted to develop Newtonian style abstractions and not the Aristotelian mishmash abstractions.
I want anyone to be able to do it. Meaning, you don’t have to go learn something else to understand how it works. You should be able to start where you are. Just learn the process and start applying it. It needs to foster collaboration. User stories are there for a collaboration.
If I’m going to say, “The user story thing doesn’t work as well as we want it to,” I should at least give a way to have some collaboration back in.
This is Conal Elliott. This process was largely inspired by work he did called the Denotational Design. It’s a different process, mind diverged because we had different objectives, but it’s largely inspired by this and his is really good, so I want to give it credit.
That’s what width and height. W and H is the width and height of the Canvas. This is just clearing. It’s actually being drawn down there, but it’s black so you can see it. If I want to draw a rectangle on top of it, I can set the color to red, that’s 25500. I set the X and Y, and the width and height.
I dip my paint brush in red and I draw the rectangle. Canvas lets you translate. I’m translating first. I take a step, I dip my paint brush, and then I draw. It lets you rotate. I’m going to rotate, and then step, and then dip, and then draw. If I rotate after I translate, that’s like I’m going to step, and then turn, and then draw.
You get a different answer if you rotate and translate. I’ve been able to turn this into physical steps that I take. That’s not how I think about art and painting and stuff. This is not the abstraction that I want. This is a fine abstraction, it’s very mathematical, but I want something else.
Here are the steps that we’re going to go through, four steps. First, we’re going to develop a physical metaphor just like I had a metaphor that I reverse engineered from Canvas. Then we’re going to explore this metaphor, refine it a little bit. Then we’re going to construct the model that will actually run. Then we’ll implement it.
You notice that the fourth step is the only one where we’re actually implementing it. That’s three steps of just thinking. I was always taught this that you should think about what you’re going to write before you write it. The problem with that advice is that like, “What do I suppose to think? At least when I write it, I’ve got something concrete?”
Also, thinking is indistinguishable from procrastination. I never liked this advice even though I knew it was true. It just wasn’t detailed enough to actually follow. This is something you can do. It’s like three steps of thinking that will fulfill this advice.
Step one, physical metaphor. Here’s the reason behind the physical metaphor. We have grown up in this world. From day one, we’ve been exploring, touching stuff and putting it in our mouths. Trying to walk, and throwing, and banging. We’re learning how the physical world works.
We have very rich, deep intuitions about how things work. We can ask our intuition, “How would this work in this physical scenario?” Then all we have to do is code that up. Too often, we start with the code and we don’t have anything to ground it with. Certainly, for beginners, this really helps get something solid. It also helps me to think of the metaphor first.
When you’re choosing a metaphor, there’s two things you’ve got to look at. One is that it has to answer questions. You have to be able to ask it, “How would it work in this situation?” You get an answer. If I say for vector graphics, we’re going to use a metaphor as a bag of marbles. That’s not going to help you. It’s a physical metaphor, but it’s not going to help you develop graphics.
The next thing is it needs to be a shared experience. Like I said, when doing collaboration, it has to be something that someone else can talk to you about. It’s actually quite magical. Often in my experience, what happens is, I’ll have this great idea. I want to go talk to someone about it. They’re like, “It sounds awesome, but I have no idea what you’re saying.” It’s too abstract.
Then I’ll go run, write down some code to try to explain it. Then I’ll show it to them and they’ll be like, “Why do you use tabs instead of spaces?” I’m like, “No, you’re missing the point.” This is something that we can actually talk about, that is shared, and is not focusing on the wrong thing.
Here are some examples of metaphors we could use for vector graphics. You can see that they’re all different. They all would give you different answers but they’re all good. Painting, you can imagine, if I paint blue over red, there’s going to be some color mixing. That would be part of our model.
Stencils would be like shapes that you can repeat. You spray paint, the stencils.
Anyway, I’m not going to go over all of them. I just want to make that point that you have choices here with what your metaphor is. They could all be good and you could just come up with different systems for it.
What we’re going to be doing is shapes and construction paper. Really quickly, you grab a piece of colored paper. You cut out a shape with scissors. Then you can move that shape around on your Canvas. That’s really easy. Already you can see with this physical metaphor, you already know how this is supposed to work because it’s a shared experience.
I’ve given this talk before. One of the first objections people have is, “Well, your abstraction might have a metaphor, but mine doesn’t.” The thing is you can tell they asked it so fast they didn’t even think about it. They didn’t even try to find a metaphor. My challenge to you is to dig deeper than that. I really think that maybe there might be something in there.
The worst case is you can ask yourself, “How would I do this with pen and paper if I didn’t have a computer and I had to do it physically?” If you’re doing a student registration system at a university, you’d say, “Well, we’ll have a desk with someone sitting at the desk and they have a book.” A student walks in and says, “I want to take this class.” Will you flip to this certain page in the book and you write your name down?
You can come up with the process that you can start discussing before you’ve even started to implement it. If you started to implement, it would be like, “Well, let’s put a database table in there.” It’s like, “Whoa, whoa, whoa, that’s too fast.” [laughs] Think about it a little bit first.
We’ve got our physical metaphor. It’s rich enough for what we want. This is just a summary of what I’ve said. We’ve got this rich physical intuition. We want to be able to mine it to get these answers. Something that we can share and it keeps us grounded. We’re not talking about cues and buffers and stuff yet. We’re still talking about something that we can all imagine.
In this step, where we’re exploring the metaphor, we’re starting to make it more precise. We’re starting to make it into something that we can move toward. A model like Newton had. The elegant, concise laws and stuff. What we’re doing is focusing on the interface first.
I was always given this advice too. When I was developing this, I was like, “Wow, this advice finally came up.” What I would do is go to the Whiteboard and I just start writing out some ideas. I have them in a certain order that and I start to look, “OK, cutouts have a color.” I take a piece of paper, it’s got to color and I start cutting out a shape. They’re going to have a shape.
Again, this is just freeform brainstorming. I don’t know where this is going yet. Overlay Order. I want to make sure that if I put a green square on top of a red circle that I’m going to see the green and not the red underneath. The order is going to matter because if I do it in the other order, the red would show up.
Rotation and translation are independent. I did this Canvas thing, I don’t want to have that property where if I rotate first and then translate, it gives you a different answer.
I put a paper here, and I move it. I rotate it, and then I move it again and then rotate. I don’t want to think about what order I’m doing stuff in. I just want to be able to move it and look at it and move it again and that kind of thing.
Same with rotation, I want to be able to say that if I rotate it a little bit and I look at it and then I rotate it again, it’s the same as doing it all at once. It’s like it adds. The same with translation, I move it here. No, no. I think, yeah, down there. That’s right. That’s how I want to play with the system.
Then I start to make it even more precise. I’m keeping a list up here of all the concepts that I’ve developed so far. I’m just saying, “OK, cutouts have a shape. Well, that means I’m going to be able to query that cutout for its shape.”
I’m thinking the name of the shape, something like a string with square or circle, right? That’s just my thought right now. Same with color, it’s going to have some kind of RBG representation, but I’m going to be able to ask a cutout for its color. Then I’m going to be able to construct different shapes of circle and rectangle.
Rectangle is going to take a color, and a width, and a height. This is kind of how you think about this. Now, then overlay. I’m going to have this operation called overlay. It’s going to take two cutouts. It’s going to put one on top of the other. I want to make sure that doing it this way is different so I’m going to use a not equals.
Then, I see a problem. Do you all see the problem? The problem is what does overlay return? Does it return another cutout? If it does, a cutout needs a shape and a color. I just said that two slides ago. What is the color of a green square on top of a red circle? Doesn’t make sense, right?
It could say, “Well, it returns a list of colors,” but then that doesn’t make sense either. Why would I care what the list of colors is? The same with the shape. What is the shape of a square on top of a circle arbitrarily overlapping? It doesn’t have a name. What we have here is called a corner case where if I return a cutout, I’ve got some cutouts don’t have a shape and color.
What happens when you have a corner case is…It’s a branch, basically. The minimum is two possible branches. I have the color or I don’t. Now, that means that somewhere else in my code where I need to use the color of a cutout, some cutout is going to arbitrarily be passed to my code. I don’t know where it came from. I don’t know if it’s an overlay or a rectangle that was directly constructed.
I’m going to have to ask it, “Do you have a color?” before I query the color and start doing something with it. Same with the shape. What you have is then four branches. You have, “If it has a shape and it has a color, then do this. If it has a shape, but it doesn’t have a color, do this.”
You’re just making more branches in the rest of your code, not even in the code to implement this thing but elsewhere. You’re increasing the complexity of the code that uses this abstraction. We just want to avoid them. If you imagine we had one more thing that was conditional, that would multiply again, so times two, 4*2=8. That’s eight branches that we have to deal with elsewhere in our code.
At this stage, we just want to avoid that, just pointing it out. Newton has no corner cases, which is why you can build on top of it for 300 years. I’m scrapping what I just did, that overlay thing. I’m making it again. This time, it’s going to have a thing called a collage.
It’s like a collection. I’m going to start with an empty collage, add a cutout to it, add another cutout to it, add another cutout to it. The collage does not have a shape or color, so that’s easy. I just need to make sure that if I put A on and then B, it’s different from putting B on and then A. That’s what I’ve got here. I can move forward now. Of course, I did mark collage up here just to be sure.
Translation and rotation independence, what does that mean? I’m trying to come up with a very concise and precise mathematical type notation for this. What I’m saying is if I rotate and then translate, it’s the same as translating and then rotating.
If I do this and then this, it’s the same as this and then this. I’ll get the same picture. Rotation is additive, same thing. I’m making a concise mathematical notation. If I rotate by R1 and then rotate by R2, it’s the same as rotating by R1+R2. Same thing. Then translation, it’s the same, just too long for one line.
Then I look at this, and look how many concepts we have. That’s seven. It just feels wrong. It feels like this is too much for this simple vector graphic system with two shapes, just color and shape. It doesn’t make sense. Too much, so I’m going to scrap it. The first thing I’m going to think about is that color and shape to get rid of because they’d given me a problem before.
When I think about it, I’m never going to query the color. I just want it to draw right. I just want it to give me the final picture that’s correct. Same with shape, why would I even expect to have a name for every shape I want to cut out? It doesn’t make sense. It’s got to draw or write. If I construct it, then I expect it to always draw it correctly.
Also, we didn’t come up with the problem until we got to a more complicated operation, the overlay operation. Once we started to compose two things, we realized, “This is actually not going to work.” What I suggest is to start with the composition, because it’s the hardest thing you’re going to do. It’s going to constrain the design the most.
By constraining the design, the rest of the stuff will be easy. It was easy to do the color and shape. When we got to overlay, boom, then we had problems. Let’s deal with the hard problems first. The rest will be smooth. Scrap everything. Start it again, but this time, start with overlay. I’m going back to just cut out but it doesn’t have a color and it doesn’t have a shape.
We just have this simple overlay of A. B is different from overlay of B and A. It returns a cut out.
This one is the same as before. If I rotate and then translate it, it’s the same as translating and rotating. This one is the same. Translation is the same. Just add the Xs and the Ys.
We need to construct the color. We didn’t even get this far before. It’s just the function and you give an RG and a B. It’s going to give you a representation of a color. The constructor will take that color and then some parameters for the shape. We also need an operation to draw it. It takes a cut out and it draws it. We’re done with Step 2.
The whole idea of Step 2 is just to get more precise. We’re mining our physical intuition about how this system works, this construction paper system. We’re mining it for these answers, for these operations and how they have to act to simulate this situation. We’re looking for those relationships and the concepts.
We want to start with composition. If we find corner cases, that’s a good reason to scrap it and start over.
Model construction. This is the fun one because we’re not on implementation yet, but we’ve also run out of precision that we can get out of this handwritten notation that we’ve got.
Basically, I just say, “Cut out is going to be one of these and then overlay is going to be one of those.” There are probably wrong choices for this, but there’s going to be some easy ones. What you’re trying to do is find stuff that your language gives you, the constructs that your language gives you.
They have the same properties that we wrote down before. The properties being like, “This has to be equal to that or this can’t be equal to that.”
For instance, the rotation, I said it’s additive. That means plus, which means it’s got to be a number. It’s not more complex than that. You’re looking for stuff that has the properties you’re looking for.
Me being a functional programmer, I’d love to teach this technique of using functions to represent things. This might not be the way that you’re used to seeing functions, but we’re going to look at the structure of the function by itself and to see where we can apply it. This is your basic function. It’s got inputs. These are the things that change.
Every time you run this function, you have the ability to pass in different values for your arguments. It’s got an output. We’re actually not going to use the output, but we know it’s there. We need something to represent things that don’t change. We can do that by wrapping it in an outer function.
We’ll have these variables that are in the environment in the scope that that function is defined in. Nothing outside of the outer function can modify those variables after it’s been called because they’re in its scope. If the function, the internal function, never changes them, we can say that they never change. We’ve got a way to represent things that change.
What does a color mean in Canvas? It means you set the fill style. Construct the function. Nothing that changes for a color. It’s going to be immutable. The things that don’t change are up here. That’s it. Why represent this as a function?
Why represent things as functions? Functions have this really cool property that their interface is so small, that they’re so clear how they work and how they compose. If you, for instance, did a class with 10 methods. You’re probably going to have getters and setters on it. You can so easily just cheat and skip over the clean interface, ask the color and do something.
Whereas, this way, you can even tell what the color is. Once you make one, all you can do with it is call it. You can’t make logic that knows like, “If the R is greater than 100, then do that.” But we can’t do that. You can’t cheat in any way. It’s just very clear.
Later, you can make your interface have that, but when we’re building this abstraction in this Newtonian clean room way, this is where you want to be.
A more complicated one. Rectangle. It’s only complicated because drawing a rectangle it takes more code than setting the color. The cut out is a function of TX, TY, and R.
That’s the translation and the rotation because those are the two things that change. Sorry, wrong order. The color and the shape don’t change. The translation and the rotation will change because once I cut it out of the paper, the color can’t change. I’m not going to change it after I cut it out, but I can move it around. That’s all I’m saying.
This is just the code in Canvas for drawing the rectangle. It’s going to save the state and restore it at the end so that this translate and rotate don’t affect anything else. It’s going to call the color function that will set it. We’ll just fill in the rectangle. Circles are very similar except instead. They have the same signature.
That’s important. They have the TX, TY, and R, but they have to use this arc and fill thing. It’s the same thing. It’s just different.
Here’s where it gets interesting. Notice we were able to do the easy stuff first, the color and the rectangle because we already figured out what properties they had. We can start with the easy stuff first and we move to the complicated stuff.
This is translate. Translate is going to take a cut out and an X and a Y. It’s going to return a new cut out. Notice the same signature. What it’s going to do is it’s going to call cut out but adding the new X from here to the TX from here, the Y to the TY and not changing R. Notice how nice and concise this is. That’s what I really like about. It’s nice and concise.
I didn’t need my pointer. [laughs] [inaudible 34:12] . Rotate is the same except we’re adding the rotation and R here and not changing TX and TY.
Overlay, all it does is it takes an A and a B and calls A with the TX and TY and R. It calls B with a TX, TY, and R. We have draw which has passed the zeros in and then those zeros will get added so whatever translation and rotation we have.
I said we’re not implementing it yet, but the nice thing about doing it in a programming language is that you can actually run it. You can test out in your model. I just want to be clear. At this point, we have more tools the Newton ever had. If he had a computer, he could have written a model in the computer and tested out how his physics would work.
He had to do it all in his head but we have this. We don’t even have to be as smart as Newton. We can do it on our screens. What am I doing? Setting it all the black, creating a rectangle, it’s red, 200 by 100, translate it, rotate it. The numbers don’t really matter. Sorry, it’s a white rectangle. I said red. A red circle, then I’m going to translate it.
I create an overlay where C is on top of R. That’s why you see that and then I draw it. That’s what I get. Right there. That looks right. I start exploring and I start trying different things. Let me rotate the thing. Let me translate it in another way. Let me add another thing in. I play with it for a while. I won’t show that, but I did find this situation that I didn’t like.
All of this is the same except then at the end. I’d take the R, the O and I rotated. I rotated over time so I can watch it animated. I’m going to play a movie and you see what it looks like when I rotated over time. That is not what I expected to happen. That does not sit well with my intuition about the model, about my construction paper set up.
What’s wrong? If I were Aristotle, I’m going to use him as a scapegoat. If I were Aristotle, what I would do is I would say, “There’s this problem so we’ll put an IF statement in there.” What would Newton do? He would go back to his model and he would say, “I must have missed something. Nothing wrong with the code. I forgot to bring something out of my model.”
Let’s take a look at the code. You see what’s happening is, I’m just passing R to each one. I’m rotating each one independently. I’m not rotating them together. That’s what I was expecting. I thought I’m taking my hand and I’m putting them on the two things. I’m rotating it, but notice when I do that, I can rotate.
I can grab it by the bottom right corner or I can grab it by the top left corner. I would get different results. It matters where I put my hand and that I didn’t capture it all. This is the value of being able to run it. You can see stuff that you’ve missed from your model. I need to add that place that I rotate.
That’s my advice is revisit your metaphor and go back up and before you go forward again. It gets more complicated now. This is how I decided to solve it. When I make an overlay, not only do I take the A and the B, but I give it a center point. I use that in here. It’s too much to explain but what you have to do is move it to the origin, rotate it, and then move it back.
That’s what’s happening here. You see, when I call overlay, I’m adding in the center point. When I rotate it, it looks like this. This is end of step three. We’ve got a running model. We needed to choose constructs that preserved the properties that we explored in the second step. If you have a problem, revisit your metaphor.
Don’t add more problems to it, trying to hack it to get it to work, which is what I do, too. I know the temptation. Implementation. Just use a class. Seriously, this is maybe the least important part, I want to say. You should do whatever is normal for your language. Use a class. That’s fine. That’s what I would do. Probably, I do Clojure a lot.
I’d probably just use a HashMap to represent all the data that I needed. I would just make a mental note. The color never changes but the rotation does. That’s what I would do. I’m not going to actually do the actual implementation. I feel like I showed where I want to show. I’m actually going to skip this slide or go on. There’s some corollaries to this process.
You have to know your domain. I personally feel like in my experience working as a programmer, we don’t learn our domain well enough. If we’re doing an accounting system, we should learn accounting. We should know accounting in some ways better than an accountant who does it themselves because we have to be able to model it and including all those weird cases that they might not remember.
We have to be able to put those into a mechanical system and you have to know your language. You notice we took the function and we broke it down, like what are the parts and what are they for? You need to be able to do that.
This is my newsletter. It’s about Functional Programming and Clojure. This is me on Twitter.
If you want to continue the conversation, any time, just let me know. I’m at listcast.com as well. If you want to find my email and stuff like that. Thank you very much.