Ep 118: Pure Parts
Each week, we discuss a different topic about Clojure and functional programming. If you have a question or topic you'd like us to discuss, tweet @clojuredesign, send an email to feedback@clojuredesign.club, or join the #clojuredesign-podcast channel on the Clojurians Slack. This week, the topic is: "parts of a pure data model". We look at pure data models we've created and see what they have in common. Our discussion includes: How we make pure data models How to organize pure data models What are the common parts of a pure data model? What are schemas good for? How is a functional pure data model different than an object-oriented class model? How does a pure data model help with maintenance? Semantic information verses concrete operational information. What about I/O? Input and output transforms for maximizing purity Why save information that the program can't use? Selected quotes And when there's a pure data model, a pure data model is something that is both wide enough to handle an actual use case and be useful, but it's shallow enough that you can understand it and trust the function calls that it has. All of the operations on that data are in the same namespace, so it's easier to understand. I know they're predicates because they end in a question mark. All of the necessary changes and views are encapsulated in a namespace. That means the rest of your application can rely on its higher-level operations when working with the data model. These are a higher-level vocabulary for your application, instead of just Clojure core's vocabulary. Everything that can be done is all co-located in a namespace. Am I multiplying by 0.10 or 0.15? Or am I calculating a tip? One of those statements has more information. A pure data model lets you, as a programmer, think at a higher level in the rest of your application. When you think at a higher level that's trusted, it's a lower cognitive load. You can come back to the code later, read a function, and know what it means in the context of your application. In every pure data model, you have to know what the data looks like. Don't underestimate the value of being able to find places where a predicate is used. It tells you what the code cares about this situation. When you have to nuance the situation, you can look at the call sites and take them all into account. Once you've made the HTTP call, all the information about the request, the response, the body, and all that is pure data. You can do a pure transform from the domain of raw, external HTTP information into the internal domain of the pure data model. But because it's a pure function, it's a lot easier to test. All things are easier to test when they're pure. I/O is a very, very thin layer—both on the way in and the way out. Instead of mixing I/O and logic, do as much I/O as you can, at once, to get a big bag of pure information to work with. And then on the way out, do a pure transform to generate everything you need for the I/O, like the full requests. You can have a big bag of extra context that's there for you as the programmer—even though the program doesn't need it. Parts of a pure model Data tree Schema Literals (eg. initial state) Predicates Data operations Reducing function (state + event) Transforms in Transforms out Views (special kind of transform) Links "Reentrant Coding" Series Ep 114: Brand New, Again Ep 115: The Main Event Ep 116: The Main Focus Ep 117: Pure Understanding
Ep 117: Pure Understanding
Each week, we discuss a different topic about Clojure and functional programming. If you have a question or topic you'd like us to discuss, tweet @clojuredesign, send an email to feedback@clojuredesign.club, or join the #clojuredesign-podcast channel on the Clojurians Slack. This week, the topic is: "pure data models". We find a clear and pure heart in our application, unclouded by side effects. Our discussion includes: What is the heart of a Clojure application? Pure data models! What is a pure data model? Why do we use pure data models? How do they compare to object-oriented data models? Where do you put pure data models? How do you organize your code? How pure data models avoid object-oriented dependency hell. How do pure data models help you understand the codebase quickly? Why does a codebase become easier to reason about by using pure models? How do pure models fit into the overall application? How do pure models relate to state and I/O? Examples of pure models Selected quotes It's functional programming, so we're talking about pure data models! That is our core, core, core business logic. A pure data model is pure data and its pure functions. No side effects! We already have a whole set of Clojure core functions to operate on data, so why would we have functions that are associated with just this pure data? Because you want to name the operations, the predicates, and all the other things to do with this data, so that you, as a human, understand. Those functions are high-level vocabulary that you can use to think about your core model. They are business-level functions. They are super-important, serious functions. We don't like side effects, so we define an immutable data structure and functions that operate on that data. They cannot update that data. They can't change things in place. They always have to return a new version of it. At a basic level, you have functions that take the data. They give you a new data tree or find something and return it. We like having the app.model namespace. You can just go into the app/model folder and see all of the core models for the whole application. Any part of the application can have access to the model. The functions are the interface. All you can do is call functions with pure data and get pure data back. You can't mess anything up except your own copy. It's just a big pool of files that are each a cohesive data model. They're a resource to the whole application, so anything that needs to work with that data model will require it and have all the functions to work with it. With pure models, there's no surprise! In OO, the larger these object trees get, the more risk there is. Any new piece of code, in the entire codebase, has access to the giant tree of objects and can mess it up for everything else. Pure models lower your cognitive load. The lower the load is, the more your brain can focus on the actual problem. You can read the code and take it at face value because the function is 100% deterministic given its inputs. If it's a pure function, you don't have to wonder what else is happening. The model directory is an inventory of the most important things in the entire application. Here are all the things that matter. As much code as possible should be in pure models. Look at the unit tests for each pure model to understand how the application reasons and represents things. It's the very essence of the application. A lot of times in functional communities, we say "keep I/O at the edges." Imagine one of these components is like a bowl. At the first edge, there's I/O. In the middle is the pure model goodness. On the other side is I/O again. None of the I/O is hidden. That's the best part. Because I/O isn't hidden behind a function, it's easier to understand. Cognitive load is lower. You can read the code and understand it when you get back into it and you're fixing a bug. The shallower your I/O call stacks are, the easier they are to understand. Where there are side effects, you want very, very shallow call stacks, and where there are no side effects, and you can unit test very thoroughly, you don't have to worry about the call stack as much. Links "Reentrant Coding" Series Ep 114: Brand New, Again Ep 115: The Main Event Ep 116: The Main Focus Ep 029: Problem Unknown: Log Lines
Ep 116: The Main Focus
Each week, we discuss a different topic about Clojure and functional programming. If you have a question or topic you'd like us to discuss, tweet @clojuredesign, send an email to feedback@clojuredesign.club, or join the #clojuredesign-podcast channel on the Clojurians Slack. This week, the topic is: "frontend matters". We turn our attention to the frontend, and our eyes burn from the complexity. Our discussion includes: What is a Single Page Application (SPA)? The progression from static websites, to multi-page websites, to JavaScript-enhanced pages, to SPAs. Why we like SPAs. Where's the "main" for the frontend? The complexity of frontend builds. How does the frontend app start running? How does the "table of contents" concept apply on the frontend? How can you make the most important parts of the application clear? What are the most important parts of a SPA? What is the "backend of the frontend"? The asynchronous, reactive cycle that drives a SPA. Selected quotes You don't usually go into a code base just to browse it, or just to have fun. You go there with a purpose. You need to work. You need to get something done fast. We like rich frontends. We're able to do a lot more interactivity. There's less interruption when the page has to load. There are a lot of advantages to SPAs. With a SPA, it's really, really fast to switch between everything. It feels almost instantaneous because there is almost nothing to load each time. The counterpart is that a SPA is more sophisticated, so it ends up being more complicated. It's almost like a process that's running continuously. There's more code that's present in a SPA than any individual page load. From the browsers point of view, the "main" is the markup, and you have to tell it to run some code. It's just one blob of code to the browser. You can't look at that code because it's transpiled, minified JavaScript. I do think it's interesting that we've gotten several minutes into this episode, and we're still talking about how things get made into the final sausage. It's reflective of how much effort it takes to set up the JavaScript ecosystem. We make a "main.cljs" file, and that is the top of the application. It's a signpost. "Hey! Hey! Look here first!" The tab's not going to go away, so all we need to do is start up all the event listeners because JavaScript is a very event-driven language. I want "main" to be a table of contents of everything that matters in the app: the views, the routes, the URLs, browser hooks, web sockets, etc. The worst kind of "main" is no "main" at all. There are frameworks where you make a whole bunch of separate files for each of your routes. I love how many times we said the word "react" in this episode. It's all very event driven. That's just the model of the whole browser. It's the water that you swim in, so you must swim the right way in order for the application to succeed. Reactive cycle User Interaction → Event → Callback → Reactive Model → Re-Render Kinds of frontend components Navigation (eg. browser history) Router Settings State holders (eg. app state, reactive atoms) Pure models Browser integrations (eg. camera, microphone, GPS, notification API, fullscreen API) Render Links "Reentrant Coding" Series Ep 114: Brand New, Again Ep 115: The Main Event Clojure for static sites Stasis Powerpack ClojureScript for frontends shadow-cljs Figwheel Reagent
Ep 115: The Main Event
Each week, we discuss a different topic about Clojure and functional programming. If you have a question or topic you'd like us to discuss, tweet @clojuredesign, send an email to feedback@clojuredesign.club, or join the #clojuredesign-podcast channel on the Clojurians Slack. This week, the topic is: "the main function". We look for a suitable place to dig into the code and find an entry point. Our discussion includes: We're diving into a Clojure project. Where should we start? First up: the backend. Why start with the code? Why is "main" so important? What's the problem with lots of entry points? What should a good "main" do? How does a good "main" help you find things? What is a good component system? The difficulty of understanding inter-component dependencies. What is a "table of contents" for your program? Random access reading. What are common kinds of components? What is a pure model? How do pure parts and side-effecting parts all come together? Selected quotes Be friendly to people that come into your code base. I didn't trust myself as much as I should. "You just start at the top and write from the top to the bottom." "That's how I code everything. It's just one really large file." So all parts of your code are reachable from "main"—or should be. A great "main" is where you can see all the major parts of the application and how they fit together with each other. A terrible "main" is a system that doesn't have any "main" at all! It has a thousand different entry points that are all over the place. A great "main" is very compact. You can scan it. It's a very high level recipe of what's going on. Component has a system map. You can just look at the data structure and see all of the different components—the major players. The alternative is components that declare dependencies on each other. It's a kind of nightmare. Everything running independently, calling or referencing each other. What's using what? What's calling what? How does information flow through? When dependency information gets spread all over the place, you have to go to all the different places to even understand what you need. Having it all in one place is essential for understanding. It really helps when you can see the interdependency between things really easily. Each component should only get what it actually needs. It shouldn't just get the whole map of every dependency. Common kinds of components: shared resources, integrations in, integrations out, pure models, state holders, and amalgamations. It all comes together in the amalgamations. Pure models are the core of the application. They are higher level than just data manipulation. All of the actual nuts and bolts is in the pure models, and that makes the components relatively light. The goal is to make the pure model as fat as possible without introducing any non-determinism, AKA side effects. Amalgamations: it's where the "in", the "out", and the pure model all come together—where the real work gets done. The amalgamation components end up being at the middle of the application. They're a kind of orchestrator. Common kinds of components shared resources integrations in integrations out state holders pure models amalgamations Links Component Ep 097: Application of Composition. We discuss having table-of-contents style sections in your code. It helps with finding things and forming the big picture.
Ep 114: Brand New, Again
Each week, we discuss a different topic about Clojure and functional programming. If you have a question or topic you'd like us to discuss, tweet @clojuredesign, send an email to feedback@clojuredesign.club, or join the #clojuredesign-podcast channel on the Clojurians Slack. This week, the topic is: "what's old is new again". We find ourselves staring at code for the first time—even though we wrote some of it! Our discussion includes: Christoph re-opened his own code base after 14 months! Nate joined a software team with a large, legacy code base. Why does the problem of "fresh eyes" matter so much? How is my past self going to treat me? Are the past teammates going to help me? Why you shouldn't rely on memory. What's involved in really understanding a code base in order to make a change? The frustration of being unproductive. How is an app put together? What's the flow of information? How do you really know if your code is comprehensible? Selected quotes It's always fun to start something new. You don't have to worry about all those other things from the past! I was a little nervous about that other guy who made all the code: me fourteen months ago! I wasn't sure how good of a teammate he was going to be. The word "legacy" is like a big bad word, but I feel like it should be a badge of honor! It's software that is running right now and paying your salary! You should have a reverence for code that exists and is running. At some point in time, you or a new team member, is diving into a code base with fresh eyes. It brings up so many important issues that we face as developers. We spend so much time reading code and forming mental models about what is going on. A fundamental challenge in software development is understanding, comprehending and reasoning about the code base. Comprehending and reasoning about the code is one of the primary drivers behind the "why" of a lot of the so-called "best practices" of the industry. Why do you write tests? Why do you write documentation? Why do you try to have a good design in your application? There's this constant learning that we have to do, and so try to make that easier. He moved on to better projects in the sky. We've lost him to a better project in the Cloud. He moved to a better project upstate! It's easy to say: "We have great documentation! Our code is super readable! Decoupled? Absolutely! Our pure data models? Totally comprehensible!" It's easy to say that, but you really find out if those things are true when somebody new joins the team or when you have to revisit the code after a long time. Always trying to teach someone else about your code. There's always some future person. What can we do now as we're setting up the situation for new people in the future?