It's more about controlling state. I like to think of ring[0] as a really nice example of this that's pretty easy to reason about.
It abstracts http handling into a series of key/value data structures. The request that's pulled in looks like this:
{:remote-addr "localhost"
:headers {"host" "localhost"
"content-type" "application/json"
"accept" "application/json"}
:server-port 80
:content-type "application/json"
:uri "/"
:server-name "localhost"
:query-string nil
:body ""
:scheme :http
:request-method :get}
The response like this:
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"}
Functions manipulate these structures, but their input and outputs are primarily those data structures.
(defn handler [request]
{:status 200
:headers {"Content-Type" "text/plain"}
:body "Hello World"})
Your app is now just a function stack that get's called in this order:
(def app
(-> handler
(wrap-content-type "text/html")))
Request => wrap-content-type => handler (generate response) => wrap-content-type => Returned
Want to add middleware? Define a function that handles it, here's an example[1]:
(defn middleware [handler]
(fn [request]
;; Do something to the request before sending it down the chain.
(let [response (handler request)]
;; Do something to the response that's coming back up the chain.
response)))
Then add the function to your app:
(def app
(-> handler
(wrap-content-type "text/html")
(wrap-keyword-params)
(wrap-params)))
You want to handle session data? Ring provides wrap session[2], add it and then use your own middleware or handler to manipulate the session by just changing the value at :session.
Example [3]:
(defn handler [{session :session}]
(let [count (:count session 0)
session (assoc session :count (inc count))]
(-> (response (str "You accessed this page " count " times."))
(assoc :session session))))
Much of your world is now reduced down to:
* Does this data structure look correct?
* How can I fix it?
* Do I need some resource like a db connection? Ok how do I add it to my data structure?
It all composes, and you end up worrying a lot less.
Finally I find that doing things this way ends up creating programs that just have much less explicit state, as opposed to temporary state that's derived from other values and so only changes when those things change.
You can program in other languages like this, it's just a bit harder, and you have to hope that no one else you're working with breaks one of the assumptions that a system like this is built on, as opposed to having the language enforce those for you.
Sorry if I've been unclear =)...
[0] - https://github.com/ring-clojure/ring/wiki (Examples taken from ring).
[1] - https://stackoverflow.com/a/19459508
[2] - https://github.com/ring-clojure/ring/blob/95e4ca25d5b98c45f9...
[3] - https://github.com/ring-clojure/ring/wiki/Sessions