As one small brain to another I can give you answers that work for me.
First, as to why immutability. It is important to understand every possible advantage as you pay such a high price for it. It allows you to reason about your code even when doing multi-threading.
Not everything is immutable though. Things that are not though often come with different types of guards to ensure thread safety. Like thread-local variables, atoms, agents and refs. Also, when you need to, you can turn immutability off for most immutable structures for a major performance gain. But you gotta make sure that the access scope is then severely limited or it will get tough to reason about your code.
To survive immutability, you do a lot with sequences. Much of the Clojure API is about sequences. And sequences can be lazy. Like scala, lazy is encouraged but often means there is a sync lock under the covers. :0
Objects are discouraged, as they are hard to write the code for, maintain immutability and performance, and get anything useful done. Records are recommended, which are app-optimized maps with lots of implicit code to help with immutability.
Strangely, iterators are used under the covers. These are used to turn structures into sequences. They implement the Java Iterator interface, and are fully mutable. And yeah, they are real objects. You can even write them in Clojure, though most are written in Java. (Clojure is not yet written in Clojure, but there are projects in progress to do that.)
Below I've included my tree map iterator and sequencer. Early versions. And not typical code at all. Both use deftype rather than defrecord, giving you greater speed but leaving immutability in your own hands. And the iterator is NOT immutable!!! I also see a bug in the sequencer, but it is also present in the Java code I am translating into Clojure and that Java code is part of the implementation of Clojure. :D (OK, it is a pretty small bug.)
Note that Clojure supports all aspects of OO, for full bi-directional interoperability with Java. But it discourages inheritance most strongly. Though you CAN do subclassing with gen-class. And it can be pretty awkward at times.
Let me conclude that Clojure is a very idiomatic language that adds a lot of data structures and sequence functions to lisp. As well as a lit of weird things to make Java interoperability possible. It is quite dense, and I think it could eventually be fun to use, though I am not there yet by a long shot!
Bill
(ns aa-collections.immutable.map-iterator
(:require [aa-collections.immutable.map-node :refer :all])
(:import (clojure.lang Counted IMapEntry)
(java.util Iterator)
(aa_collections.immutable.imap_node IMapNode)
(aa_collections.immutable MapSequence)))
(deftype map-iterator [^IMapNode node
^{:volatile-mutable true IMapEntry true} lst
^{:volatile-mutable true int true} cnt]
Iterator
(hasNext [this]
(> cnt 0))
(next [this]
(if (nil? lst)
(set! lst (first-t2 node))
(set! lst (.next-t2 node (.getKey lst))))
(set! cnt (- cnt 1))
lst)
Counted
(count [this] cnt))
(defn new-map-iterator [node]
(->map-iterator node nil (.-cnt node)))
(defn ^MapSequence new-map-seq [^IMapNode node]
(MapSequence/create (->map-iterator node nil (.-cnt node))))
(ns aa-collections.immutable.MapSequence
(:gen-class
:main false
:extends clojure.lang.ASeq
:implements [clojure.lang.Counted]
:constructors {[java.util.Iterator]
[]
[clojure.lang.IPersistentMap Object]
[clojure.lang.IPersistentMap]}
:init init
:state state
:methods [^:static [create [java.util.Iterator] Object]]
:exposes-methods {count superCount})
(:import (aa_collections.immutable MapSequence)))
(defn -create [iter]
(if (.hasNext iter)
(new MapSequence iter)
nil))
(deftype seq-state [iter val rst])
(defn -init
([iter]
(let [s (->seq-state iter (atom nil) (atom nil))]
(reset! (.-val s) s)
(reset! (.-rst s) s)
[[] s]))
([meta s]
[[meta] s])
)
(defn -withMeta [this meta] (new MapSequence meta (.-state this)))
(defn -first [this]
(let [s (.-state this)
v (.-val s)]
(if (= s @v)
(swap! v #(if (= s %) (.next (.-iter s)))))
@(.-val s)))
(defn -next [this]
(let [s (.-state this)
r (.-rst s)]
(when (= s @r)
(-first this)
(swap! r #(if (= s %) (-create (.-iter s)))))
@(.-rst s)))
(defn -count [this]
(let [iter (.iter (.-state this))]
(if (counted? iter)
(.count iter)
(.superCount this))))