Functional Classes in Clojure
source link: http://blog.cleancoder.com/uncle-bob/2023/01/19/functional-classes-clojure.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Functional Classes in Clojure
My previous blog 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.
Trigger Warning:
- Object Oriented Terminology.
- Dynamically Typed Language.
- Mixed Metaphors.
- Distracting Animations.
To all the adherents of the Statically Typed Functional Programming religion: I know that you believe that Static Typing is an essential aspect of Functional Programming and that no mere dynamically typed language could ever begin to approach the heights and glory of The One True and Holy TYPED Functional Apotheotic Paradigm. But we lowly programmers quivering down here at the base of Orthanc can only hope to meekly subsist on the dregs that fall from on high.
(R.I.P. Kirstie Alley
OK, so, once again…
A class is an intentionally named abstraction that consists of a set of narrowly cohesive functions that operate over an internally defined data structure.
We do not need the class
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.
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).
So… Here come your father’s parentheses!
(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)))
Some years back I wrote a nice little spacewar game in Clojure. You can play it here. While playing, if you manage to blow up a Klingon, a sparkling cloud of Dilithium Crystals will remain behind, quickly dissipating. If you can guide your ship into the midst of that cloud, you will harvest some of that Dilithium and replenish your stores.
The code you see above is the class that represents the Dilithium Cloud.
The first thing to notice is that I defined the TYPE of the cloud
class – dynamically.
A cloud
is an object with an x
and y
coordinate, and a concentration
; all of which must be numbers. I also created a little type checking function named valid-cloud?
that is used by my unit tests (not shown) to make sure the TYPE is not violated by any of the methods.
Next comes make-cloud
the constructor of the cloud
class.
There are two overloads of the constructor. The first takes no arguments and simply creates a cloud
at (0,0) with no Dilithium in it. The second takes three arguments and loads the instance variables of the class.
There are two primary methods of the cloud
class: update-clouds-age
and update-dilithium-harvest
. The update-clouds-age
method finds all the cloud
instances in the world
object and decreases their concentration by the decay
factor – which is a function of the number of milliseconds (ms
) since the last time they were updated. The update-dilithium-harvest
method finds all the cloud
objects that are within the ship
object’s harvesting range and transfers Dilithium from those cloud
objects to the ship
object.
Now, you might notice that these methods are not the traditional style of method you would find in a Java program. For one thing, they deal with a list of cloud
objects rather than an individual cloud
object. Secondly, there’s nothing polymorphic about them. Third, they are functional, because they return a new world
object with new cloud
objects and, in the case of update-dilithium-harvest
, a new ship
object.
So are these really methods of the cloud
class? Sure! Why not? They are a set of narrowly cohesive functions that manipulate an internal data structure within an intentionally named abstraction.
For all intents and purposes cloud
is a °°°°°° °°°°°°° class.
So there.
Recommend
-
110
I found previously that CDS and AOT can have a dramatic effect when used to speed up JVM startup for a simple “Hello World”. Now, I want to see how effective that ac...
-
135
-
150
inf-clojure This package provides basic interaction with a Clojure subprocess (REPL). It's based on ideas from the popular inferior-lisp package. inf-clojure has two components - a nice REPL buffer (...
-
39
© Shutterstock / Rawpixel.com What’s the state of the Clojure ecosystem in 2019? According to Clojure’s annual survey, Clojur...
-
11
Make your Ruby code more modular and functional with polymorphic aggregate classesHi, weʼre arkency 👋 For the last year, I’ve been using Rails (as I do for the last 10 y...
-
7
Why and How To Use Them In Functional ProgrammingClasses, Interfaces and Object Expressions in F# | Why and How To Use Them In Functional Programming - YouTube Back PH Skip navigation
-
5
Thoughts On Java 8 Functional Programming (and also Clojure) After working with Java 8 for the better part of a year, I have to say I find its new “Functional” features both useful and aggravating at the same time....
-
4
Ski Posted on Feb 15...
-
3
Functional Classes 18 January 2023 I recently tweeted the following: Should you subdivide a functional program into classes the way you would an object oriented program?Yes. You sh...
-
8
For more experienced functional programmers, most of this post will seem introductory, but I introduce some more advanced stuff near the end.Functional Programming over Canadian Program...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK