Sunday, April 17, 2022

Mutable versions of common data structures

Clojure encourages immutability and functional programming as a best practice. Nonetheless, you can still access mutable data structures using Clojure's Java interop facilities. Many of the distinctions between immutable and mutable data structures are first apparent in Java itself. For example, the distinction between immutable Strings and the more mutable StringBuilders.

Similarily, in Java all primitive data types are immutable. The mutable versions of these data types instead come from the apache commons lang module. So getting a grasp first of the difference between mutable and immutable data structures in Java will give you knowledge that that can transfer over to Clojure. This data structure knowledge will be summarized here.

Primitives

Mutable booleans:
The java.lang.Boolean class is a value type and it is therefore immutable. Therefore, in order to create a mutable boolean you should make use of apache commons lang.
; create a mutable boolean
(def mutable-boolean (new MutableBoolean))

; set the value of the mutable boolean to true
(.setValue mutable-boolean true)

; print out the value of the mutable boolean
(println (.getValue mutable-boolean))
The MutableBoolean class comes with setTrue and setFalse operators that don't take any extra arguments on the stack. An example of this usage is provided by the swap-boolean! operation.
(defn swap-boolean!
  [bool]

  (if (.isTrue bool)
    (.setFalse bool)
    (.setTrue bool)))
The basic swap operation isn't provided in the current version of apache commons lang, but as demonstrated above it isn't too hard for us to provide it ourselves.

Mutable numbers:
The other class of mutable primitives provided by apache commons lang are the mutable numbers. These come in many forms for each type of number in java.lang but right now we will mainly focus on mutable integers.
; create a mutable number
(def mutable-int (new MutableInt 0))

; perform arithmetic side effects on a number
(.add mutable-int 10)
(.subtract mutable-int 10)

; print out the value of the mutable integer
(.println (.getValue mutable-int))
In the previous section we define the swap-boolean! operation on mutable booleans. We will now define an imperative operation on numbers, that takes a given number and produces its square.
; functional square number function
(defn square
  [n]

  (* n n))

; imperative square number function
(defn square!
  [n]

  (.setValue n (square (.getValue n))))
With these out of the way, how would you define an imperative version of the factorial function using Clojure? We can actually achieve this using while loops. Programming this way, you can almost forget you are using Clojure and not Java.
; imperative factorial function
(defn factorial
  [n]

  (let [i (new MutableInt 1)
        rval (new MutableInt 1)]
    (while (<= (.getValue i) n)
      (.setValue rval (* (.getValue rval) i))
      (.increment i))
    rval))
Here is one more simple example that demonstrates the use of mutable integers provided by apache commons lang.
; imperative calculation of square pyramidal numbers
(defn square-pyramidal-number
  [n]

  (let [i (new MutableInt 1)
        rval (new MutableInt 0)]
    (while (<= (.getValue i) n)
      (.add rval (square (.getValue i)))
      (.increment i))
    rval))
Mutable number types like these are the one missing thing in Clojure. Apache commons lang resolves that issue for you. Writing imperative Clojure is a lot like writing Java, so you have to decide for yourself what kind of mix of languages and practices you want to go with.

Strings

Mutable strings:
The java standard library provides two different classes to support mutable string types: the StringBuilder and the StringBuffer. The difference between them is one of synchronization.
  • StringBuffer: a thread-safe mutable string type. String buffers are safe to be used on multiple threads and methods on them are synchronized.
  • StringBuilder: a compatible API with StringBuffer but with no support for synchronization. It is faster when used is single threaded programs.
So you have your choice of how you want to get mutable strings. In this case, these are provided by the Java standard library available with any Clojure distribution so you don't need to separately acquire apache commons lang.
; create a mutable string
(def string-builder (StringBuilder.))

; convert the string builder back to a string
(def string (.toString string-builder))
The major way that you perform side effects on a StringBuilder is by using the append method which is overloaded to handle characters, strings, and primitive data types. Here is an example that creates a String storing the alphabet by using its encoding in ASCII:
(def alphabet
  (let [str (StringBuilder.)]
    (doseq [i (range 97 123)]
      (.append str (char i)))
    (.toString str)))
This implements an imperitive version of the String reversal method using the StringBuilder class. As the underlying String is immutable this doesn't have to use the temporary store and swapping method of reversing a mutable string in place.
(defn build-reverse-string
  [str]

  (let [rval (new StringBuilder "")
        i (new MutableInt (dec (count str)))]
    (while (<= 0 (.getValue i))
      (.append rval (.charAt str i))
      (.decrement i))
    (.toString rval)))
So this is one more example of a mutable data structure that you can master which may be useful in either Java or Clojure. In both languages Strings are immutable by default, but mutable versions are also provided for you.

Mutable collections

Arrays:
Java arrays are inherently mutable, so we can always modify them using Clojure's Java interop functions lke aget and aset. A familiar use of Java arrays is in creating prime number sieves.
(defn sieve
  [n]

  (let [primes (boolean-array (repeat (inc n) true))
        p (new MutableInt 2)]
    (aset primes 0 false)
    (aset primes 1 false)

    (while (<= (square (.getValue p)) n)

      (when (aget primes (.getValue p))
        (let [i (new MutableInt (square (.getValue p)))]
          (while (<= (.getValue i) n)
            (aset primes i false)
            (.setValue i (+ (.getValue i) p)))))

      (.increment p))

    primes))
This prime number sieve lists out all the prime numbers up to a number n in a mutable boolean array. In addition, this uses apache commons language mutable integers to demonstrate the most imperative Clojure.

Mutable sets:
In Clojure an immutable set is created by a collection of elements surrounded by braces like #{1,2,3}. Sets are therefore immutable by default. In order to create an immutable set, you can use HashSet and similar classes of the java.util collections framework.
(defn index-set
   [coll]

  (let [rval (new HashSet)]
    (doseq [i (range (count coll))]
      (when (aget coll i)
        (.add rval i)))
    rval))
The above example simply takes the list of indices in a boolean array such as those produced by a sieve and it iteratively adds elements to a HashSet when it finds indices that have true values determined by aget.

Mutable lists:
Mutable lists and ordered collections are also provided by the java.util collections framework by ArrayLists and Vectors among other classes.
(defn sorted-index-set
  [coll]

  (let [rval (new ArrayList)]
    (doseq [i (range (count coll))]
      (when (aget coll i)
        (.add rval i)))
    rval))
The above example does the same thing as the index-set but it produces an ordered result by keeping track of the order of elements append to the collection. This could also be applied after the sieve method.

Mutable maps:
Clojure supports immutable maps by collections of pairs of elements contained between braces like {:x 1, :y 2}. On the other hand, mutable maps can be created by java.util.HashMap.
(def indices (new java.util.HashMap))
(.put indices "first" 1)
(.put indices "second" 2)
In order to transfer a list, vector, set, etc from its immutable Clojure form into a mutable Java form you can always use the addAll method. This applies to Clojure's immutable collections because they all implement the Collection interface. It isn't too hard, however, to create a method to insert elements of a Clojure map collection into a HashMap.
(defn add-entries!
  [rval coll]

  (doseq [[i v] coll]
    (.put rval i v)
    rval))
These different types of map types allow us to freely choose the kind of programming we want to do. Mutable data structures are more amenable to imperative programming and immutable data structures are more amenable to functional programming. With the right data structures, you can use either approach in Java or Clojure.

References:
Commons lang

No comments:

Post a Comment