Max Forasteiro
Max Forasteiro

Reputation: 382

Save state on clojure

I'm starting on functional programming now and I'm getting very crazy of working without variables.

Every tutorial that I read says that it isn't cool redefine a variable but I don't know how to solve my actual problem without saving the state on a variable.

For example: I'm working on a API and I want to keep the values throught the requests. Lets say that I have a end-point that add a person, and I have an list of persons, I would like to redefine or alter the value of my persons list adding the new person. How can I do this?

Is there ok to use var-set, alter-var-root or conj!?

(for the api i'm using compojure-api and each person would be a Hash)

Upvotes: 1

Views: 1159

Answers (2)

Carcigenicate
Carcigenicate

Reputation: 45826

You'll likely need a mutable state somewhere in a large application, but one isn't necessary in all cases.

I'm not familiar with compojure, but here's a small example using immutability that might be able to give you a better idea:

(loop [requests []
       people []

   (let [request (receive-request)]
     ; Use requests/people

     ; Then loop again with updated lists
     (recur (conj requests request)
            (conj people (make-person request))))])

I'm using hypothetical receive-request and make-person functions here.

The loop creates a couple bindings, and updates them at each recur. This is an easy way to "redefine a variable". This is comparable to pure recursion, where you don't mutate the end result at any point, you just change what value gets passed onto the next iteration.

Of course, this is super simple, and impractical since you're just receiving one request at a time. If you're receiving requests from multiple threads at the same time, this would be a justifiable case for an atom:

(defn listen [result-atom]
  (Thread.
    (fn []
      (while true ; Infinite listener for simplicity
        (let [request (receive-request)]
          (swap! result-atom #(conj % (make-person request))))))))

(defn listen-all []
  (let [result-atom (atom [])]
    (listen result-atom)
    (listen result-atom)))
    ; result-atom now holds an updating list of people that you can do stuff with

swap! mutates the atom by conjoining onto the list it holds. The list inside the atom isn't mutated, it was just replaced by a modified version of itself. Anyone holding onto a reference to the old list of people would be unaffected by the call to swap!.

A better approach would be to use a library like core/async, but that's getting away from the question.

The point is, you may need to use a mutable variable somewhere, but the need for them is a lot less than you're used to. In most cases, almost everything can be done using immutability like in the first example.

Upvotes: 2

fixxer
fixxer

Reputation: 240

Clojure differentiates values from identity. You can use atoms to manage the state in your compojure application.

(def persons (atom [])) ;; init persons as empty vector
(swap! persons #(conj % {:name "John Doe"})) ;; append new value

You can find more in docs:

https://clojure.org/reference/atoms

https://clojure.org/reference/data_structures

https://clojuredocs.org/clojure.core/atom

Upvotes: 6

Related Questions