Nov 11 2013Comments
Nov 07 2013Comments
In the world of object-oriented programming, it is common to create classes to represent data elements from your domain. These classes run into all kinds of trouble. First, they tend to breed closely coupled classes like DTOs and XML type mappers. Second, they rarely contain any intelligence and sometimes don't contain any behavior at all. Third, proliferating concrete classes can make it hard to see common abstractions trying to escape.
In the past, I answered all of those objections by arguing that they were "merely" bad design. It is possible to do good OO design that doesn't suffer from those failings. I've reluctantly come to believe that the majority of OO code will never escape those failings. Common style, frameworks, and tools all push toward a design dominated by "god classes" and data containers.
Instead of fighting that, I've decided to embrace it. If your objects are just going to be data containers, then embrace the data! Don't hide it away inside objects, and don't fragment your namespace of functions over the data by splitting them across dozens of classes.
Now when I build applications in Clojure I tend not to use Clojure's OO constructs, protocols and types, for the domain data passing through the system. I've followed Rich and Stu down the path of values and data structures.
I do find Clojure's OO constructs valuable for the structure of the system itself. I like to use protocols to limn the boundaries between subsystems. They provide exactly the degree of information hiding I want. We've been evolving a common idiom for creating instances of protocols, where we have an implementation as a type together with a constructor function. The constructor function is considered public, while the type is meant to be private.
(defprotocol IMarshaller (marshall [_ data] "Marshall data, return a ByteBuffer ready to transmit.") (unmarshall [_ bbuf] "Unmarshall data from the ByteBuffer")) (deftype EdnMarshaller  IMarshaller (marshall [_ data] (io/clj->bbuf data)) (unmarshall [_ bbuf] (io/bbuf->clj bbuf))) (defn edn-marshaller  (EdnMarshaller.))
Most of what we pass across subsystem boundaries are primitives and data structures of primitives. This particular example uses Java ByteBuffer objects, but we treat them like immutable value objects so I'll call them "primitives" for the sake of argument.
That troika of protocol, type, and constructor appears fairly often, but there are some variants.
In this example, the functions on the type are small enough that you could simply reify the protocol inside the constructor function:
(defn edn-marshaller  (reify IMarshaller (marshall [_ data] (io/clj->bbuf data)) (unmarshall [_ bbuf] (io/bbuf->clj bbuf))))
This works best when the constructor only takes one set of arguments. I would split this out when there are optional or variadic arguments to the constructor function.
Using objects to represent subsystems gives us information hiding and polymorphism. That's exactly what we need. Using data structures for the application data gives us leverage over those data structures. One more thing to consider is when and how to avoid coupling data structure shapes across subsystems, but that's a topic for a future post.
Nov 01 2013Comments
Want to meet a Cognitect in person? Here's where you can find us during the month of November:
Reston, VA 11/1-11/3
NFJS Northern Virginia Software Symposium
Speaking: Stu Halloway
Sessions: Clojure in 10 Big Ideas, Narcissistic Design, Simulation Testing with Simulant, Generative Testing, core.async, Get Logical with Datalog
San Francisco, CA 11/11
Keynote: Rich Hickey
Talks: Design, Composition & Performance, Core.async - Communicating Sequential Processes using Channels, in Clojure
Oct 29 2013Comments
Oct 15 2013Comments
In this episode, we talk to Kevin Lynagh about the many interesting things he has been up to lately. These include being invited to Hacker School and producing Weathertron, a native iOS weather app written using ClojureScript.
Oct 08 2013Comments
Ambrose Bonnaire-Sergeant has made quite a splash recently with his campaign to fund development of core.typed, a library that provides a gradual type system for Clojure. In this episode, we talk extensively with Ambrose about core.typed just minutes before he launched the campaign.
Oct 07 2013Comments
When people define processes, they usually start from the beginning. They ask "What do I have? What should I do with it?" If you're lucky, they also ask "Who needs it?" but that usually comes last.
The trouble is that when you string a bunch of processes like this together in a company, you find "gaps" between them. That's where one person delivers a spreadsheet that someone else has to manipulate more before they can use it. Or where one group delivers GUI mockups without dynamics to another group who is supposed to implement it. It leads to waste.
I often find processes with no consumers. Pure waste! Literally nobody uses the output, but the producer doesn't realize it.
When defining a process, I think you should start at the end. Define it from the perspective of the consumer.
I like to ask the questions in this order:
- Who is the consumer?
- What do they need?
- How do you deliver it to them?
- How do you know when they're ready for it?
- How do you produce it?
- What inputs do you need to produce it?
By doing it this way, you naturally create a process that includes signaling, delivers good (i.e., usable) outputs, and doesn't have any gaps.
Oct 01 2013Comments
Want to meet a Cognitect in person? Here's where you can find us during the month of October:
New York, NY 10/7-10/9
Future of Web Design
Speaking: Michael Parenteau
Talk: The Myth of the Half-brained Designer: An Artist’s Journey Through Engineering
Oct 01 2013Comments
Sep 27 2013Comments
Stu Halloway and Mike Nygard are taking over Europe. Here's where you can find them during their October adventures: