Open and Closed Systems with Clojure (Draft)



Please Note: I accidentally made this post public after writing only a rough outline on June 18, 2021. I will leave this content here for now since it's already been noticed, but I will post a link on this page to the finished version of the article once I have completed it. Please excuse the mess and my oversight.




For the purposes of this article, I propose the following definitions:

  • Members of a software system are data values and functions.
  • An open system requires only extension of itself to support new members. Consumers can leverage new members at any point in time, including never.
  • A closed system either doesn't support extension, or requires simultaneous extension of itself and its consumers when adding new members.

These definitions break down quickly if we dwell on the facts that our programs run on computers that are also running other programs, that they interact with hardware that is susceptible to physical forces of nature, that they run in time and yet there is no guarantee that the next instruction of our program will ever be executed.

But if we limit ourselves to the tamer, optimistic view of the universe that we employ when writing our programs, these concepts of open and closed systems prove useful tools for designing software systems.

Clojure—not only because it is a dynamically typed language, but also by virtue of its abstractions—encourages open systems. In this article I explore Clojure constructs that support openness and hope to highlight the benefits software engineers derive from this default.

For the Clojure examples that follow, I consider a construct open when it returns a value rather than halting or disrupting the execution of the program by throwing an exception.

Introspection

This comes up in many of the sections in this article. Need to make sure it's underscored, that these open systems are powerful because they can also be inspected by the userland program.

Truthiness

Closed true/false. Open truthy/falsey.

Functions with Infinite Arity

Arithmetic: +, -, *, /

map n collections accepting function of n args.

Sequence Functions

Core Clojure functions return nil or provide an arity for user-specified default values, rather than throwing exceptions when sought-after items are not found:

nth as an oddball exception; make note of its arity that doesn't throw an exception.

Maps

Keys of arbitrary types. Go complex enough, and maps with maps as keys become a type of pre-indexed database.

Namespaces

Private functions still available via var.

Ability to mutate a namespace from multiple files.

Ability to list all known namespaces (e.g., see implementation of apropos).

Metadata

Orthogonal to primary execution of your program, but choice of that is up to you.

Pattern of specialized members being adorned with metadata and then collected from arbitrary location by inspection of namespaces and metadata.

Multimethods

Open by default.

Use of :default for categorical defaults, not fall-backs.

Inspection of methods for enforcing comprehensive coverage over a particular domain of values.

Protocols

No methods have to be implemented, just has to be "marked" as extending the protocol.

Transducers

Completely agnostic to the collections (if any) they will work on. Composable at any level.

Closed Systems in Clojure

defstruct

Lesson learned, and defrecord does more open thing of demoting itself to a hash-map when essential keys are removed.

Pattern of [] record definitions so they never lose their type.

Conditionals: case vs. cond

Case throws an exception; cond returns nil.

Exceptions

ex-info and ex-data

One area that perhaps a sanctioned closed-system approach would benefit. Perhaps propose one here...

with-redefs

Redefined but within a specified scope.

Reader Literals

Closed by design. Syntax of base language is rich but must be circumscribed to keep programs source readable across a wide audience. Tagged literals as an acceptable compromise.

TBD: Possible Additions

  • Datalog systems inspired by Datomic?

Tags: clojure closed-system software-architecture open-system software-design

Copyright © 2024 Daniel Gregoire