piss

entries

  1. Functional Classes in Clojure
    The Clean Code Blog
  2. Functional Classes
    The Clean Code Blog
  3. Space War
    The Clean Code Blog
  4. Functional Duplications
    The Clean Code Blog
  5. Roots
    The Clean Code Blog
  6. More On Types
    The Clean Code Blog
  7. On Types
    The Clean Code Blog
  8. if-else-switch
    The Clean Code Blog
  9. Pairing Guidelines
    The Clean Code Blog
  10. Solid Relevance
    The Clean Code Blog
  11. Loopy
    The Clean Code Blog
  12. Conference Conduct
    The Clean Code Blog
  13. The Disinvitation
    The Clean Code Blog
  14. REPL Driven Design
    The Clean Code Blog
  15. A Little More Clojure
    The Clean Code Blog
  16. A Little Clojure
    The Clean Code Blog
  17. A New Hope
    The Clean Code Blog
  18. Open Letter to the Linux Foundation
    The Clean Code Blog
  19. What They Thought of Programmers.
    The Clean Code Blog
  20. Circulatory
    The Clean Code Blog

Functional Classes in Clojure

The Clean Code Blog

source

<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 (&lt; (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 #(&gt; (:concentration %) 1) clouds) clouds (doall clouds)] (assoc world :clouds clouds))) (defn update-clouds [ms world] (-&gt;&gt; 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>