Functional Classes in Clojure
The Clean Code Blog
<p>My previous <a href="http://blog.cleancoder.com/uncle-bob/2023/01/18/functional-classes.html">blog</a> seemed only to continue the confusion regarding classes in Functional Programming. Indeed, many people got quite irate. So perhaps a bit of code will help.</p>
<p><strong>Trigger Warning</strong>:</p>
<ul>
<li>Object Oriented Terminology.</li>
<li>Dynamically Typed Language.</li>
<li>Mixed Metaphors.</li>
<li>Distracting Animations.</li>
</ul>
<blockquote>
<p>To all the adherents of the <em>Statically Typed</em> Functional Programming religion: I know that you believe that <em>Static Typing</em> is an essential aspect of Functional Programming and that no mere dynamically typed language could ever begin to approach the heights and glory of <em>The One True and Holy TYPED Functional Apotheotic Paradigm</em>. But we lowly programmers quivering down here at the base of <em>Orthanc</em> can only hope to meekly subsist on the dregs that fall from on high.</p>
</blockquote>
<p>(R.I.P. Kirstie Alley</p>
<p>OK, so, once again…</p>
<blockquote>
<p><em>A class is an intentionally named abstraction that consists of a set of narrowly cohesive functions that operate over an internally defined data structure.</em></p>
</blockquote>
<p>We do not need the <code class="language-plaintext highlighter-rouge">class</code> keyword. Nor do we need polymorphic dispatch. Nor do we need inheritance. A class is just a description, whether in full or in part, of an object.</p>
<p>For example – it’s time we talked about clouds (which I have looked at from both sides now; and do, in fact, understand pretty well).</p>
<p>So… Here come your father’s parentheses!</p>
<p><img src="https://i.pinimg.com/originals/4f/1e/26/4f1e261d1afa9d58fd1125db5a5a4a12.jpg" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(ns spacewar.game-logic.clouds
(:require [clojure.spec.alpha :as s]
[spacewar.geometry :as geo]
[spacewar.game-logic.config :as glc]))
(s/def ::x number?)
(s/def ::y number?)
(s/def ::concentration number?)
(s/def ::cloud (s/keys :req-un [::x ::y ::concentration]))
(s/def ::clouds (s/coll-of ::cloud))
(defn valid-cloud? [cloud]
(let [valid (s/valid? ::cloud cloud)]
(when (not valid)
(println (s/explain-str ::cloud cloud)))
valid))
(defn make-cloud
([]
(make-cloud 0 0 0))
([x y concentration]
{:x x
:y y
:concentration concentration}))
(defn harvest-dilithium [ms ship cloud]
(let [ship-pos [(:x ship) (:y ship)]
cloud-pos [(:x cloud) (:y cloud)]]
(if (< (geo/distance ship-pos cloud-pos) glc/dilithium-harvest-range)
(let [max-harvest (* ms glc/dilithium-harvest-rate)
need (- glc/ship-dilithium (:dilithium ship))
cloud-content (:concentration cloud)
harvest (min max-harvest cloud-content need)
ship (update ship :dilithium + harvest)
cloud (update cloud :concentration - harvest)]
[ship cloud])
[ship cloud])))
(defn update-dilithium-harvest [ms world]
(let [{:keys [clouds ship]} world]
(loop [clouds clouds ship ship harvested-clouds []]
(if (empty? clouds)
(assoc world :ship ship :clouds harvested-clouds)
(let [[ship cloud] (harvest-dilithium ms ship (first clouds))]
(recur (rest clouds) ship (conj harvested-clouds cloud)))))))
(defn update-clouds-age [ms world]
(let [clouds (:clouds world)
decay (Math/pow glc/cloud-decay-rate ms)
clouds (map #(update % :concentration * decay) clouds)
clouds (filter #(> (:concentration %) 1) clouds)
clouds (doall clouds)]
(assoc world :clouds clouds)))
(defn update-clouds [ms world]
(->> world
(update-clouds-age ms)
(update-dilithium-harvest ms)))
</code></pre></div></div>
<p>Some years back I wrote a nice little <a href="http://blog.cleancoder.com/uncle-bob/2021/11/28/Spacewar.html">spacewar game</a> in Clojure. You can play it <a href="http://spacewar.fikesfarm.com/spacewar.html">here</a>. While playing, if you manage to blow up a Klingon, a sparkling cloud of <em>Dilithium Crystals</em> will remain behind, quickly dissipating. If you can guide your ship into the midst of that cloud, you will harvest some of that <em>Dilithium</em> and replenish your stores.</p>
<p>The code you see above is the <em>class</em> that represents the <em>Dilithium Cloud</em>.</p>
<p>The first thing to notice is that I defined the <em>TYPE</em> of the <code class="language-plaintext highlighter-rouge">cloud</code> <em>class</em> – <em>dynamically</em>.<br />
<img src="https://yt3.ggpht.com/a/AATXAJxJ07NzOxzlMLuiV6SGv808JXSCrALLJMXJ1w=s900-c-k-c0xffffffff-no-rj-mo" width="150" /></p>
<p>A <code class="language-plaintext highlighter-rouge">cloud</code> is an object with an <code class="language-plaintext highlighter-rouge">x</code> and <code class="language-plaintext highlighter-rouge">y</code> coordinate, and a <code class="language-plaintext highlighter-rouge">concentration</code>; all of which must be numbers. I also created a little type checking function named <code class="language-plaintext highlighter-rouge">valid-cloud?</code> that is used by my unit tests (not shown) to make sure the <em>TYPE</em> is not violated by any of the <em>methods</em>.</p>
<p>Next comes <code class="language-plaintext highlighter-rouge">make-cloud</code> the <em>constructor</em> of the <code class="language-plaintext highlighter-rouge">cloud</code> <em>class</em>.</p>
<p><a href="https://giphy.com/gifs/theoffice-the-office-tv-frame-toby-vyTnNTrs3wqQ0UIvwE">via GIPHY</a></p>
<p>There are two overloads of the <em>constructor</em>. The first takes no arguments and simply creates a <code class="language-plaintext highlighter-rouge">cloud</code> at (0,0) with no <em>Dilithium</em> in it. The second takes three arguments and loads the <em>instance variables</em> of the <em>class</em>.</p>
<p><a href="https://giphy.com/gifs/monty-python-2yP1jNgjNAkvu">via GIPHY</a></p>
<p>There are two primary <em>methods</em> of the <code class="language-plaintext highlighter-rouge">cloud</code> <em>class</em>: <code class="language-plaintext highlighter-rouge">update-clouds-age</code> and <code class="language-plaintext highlighter-rouge">update-dilithium-harvest</code>. The <code class="language-plaintext highlighter-rouge">update-clouds-age</code> <em>method</em> finds all the <code class="language-plaintext highlighter-rouge">cloud</code> <em>instances</em> in the <code class="language-plaintext highlighter-rouge">world</code> <em>object</em> and decreases their concentration by the <code class="language-plaintext highlighter-rouge">decay</code> factor – which is a function of the number of milliseconds (<code class="language-plaintext highlighter-rouge">ms</code>) since the last time they were updated. The <code class="language-plaintext highlighter-rouge">update-dilithium-harvest</code> <em>method</em> finds all the <code class="language-plaintext highlighter-rouge">cloud</code> <em>objects</em> that are within the <code class="language-plaintext highlighter-rouge">ship</code> <em>object</em>’s harvesting range and transfers <em>Dilithium</em> from those <code class="language-plaintext highlighter-rouge">cloud</code> <em>objects</em> to the <code class="language-plaintext highlighter-rouge">ship</code> <em>object</em>.</p>
<p>Now, you might notice that these <em>methods</em> are not the traditional style of method you would find in a Java program. For one thing, they deal with a list of <code class="language-plaintext highlighter-rouge">cloud</code> <em>objects</em> rather than an individual <code class="language-plaintext highlighter-rouge">cloud</code> <em>object</em>. Secondly, there’s nothing polymorphic about them. Third, they are <em>functional</em>, because they return a new <code class="language-plaintext highlighter-rouge">world</code> <em>object</em> with new <code class="language-plaintext highlighter-rouge">cloud</code> <em>objects</em> and, in the case of <code class="language-plaintext highlighter-rouge">update-dilithium-harvest</code>, a new <code class="language-plaintext highlighter-rouge">ship</code> <em>object</em>.</p>
<p>So are these really <em>methods</em> of the <code class="language-plaintext highlighter-rouge">cloud</code> <em>class</em>? Sure! Why not? They are a set of narrowly cohesive functions that manipulate an internal data structure within an intentionally named abstraction.</p>
<p>For all intents and purposes <code class="language-plaintext highlighter-rouge">cloud</code> is a °°°°°° °°°°°°° <em>class</em>.</p>
<p><a href="https://giphy.com/gifs/reaction-laughing-lotr-TcdpZwYDPlWXC">via GIPHY</a></p>
<p>So there.</p>