Search code examples
clojurefunctional-programming

How to solve "stateful problems" in Clojure?


I'm new to clojure and I'm having trouble to understand some concepts, specially pure functions and immutability.

One thing that I still can't comprehend is how you solve a problem like this in clojure:

A simple console application with a login method, where the user can't try to login more than 3 times in a 1 minute interval.

In C# for example I could add the UserId and a timestamp to a collection every time the user tries to login, then I would check if there has been more 3 than attempts in the last minute.

How would I do that in Clojure considering that I can't alter my collection ?

This is not a pratical question (although some code examples would be welcomed), I want to understand how you approach a problem like this.


Solution

  • You don't alter objects in most cases, you create new versions of the old objects:

    (loop [attempt-dates []]
      (if (login-is-correct)
        (login)
        (recur (conj attempt-dates (current-date-stamp)))))
    

    In this case I'm using loop. Whatever I give to recur will be passed to the next iteration of loop. I'm creating a new list that contains the new stamp when I write (conj attempt-dates (current-date-stamp)), then that new list is passed to the next iteration of loop.

    This is how it's done in most cases. Instead of thinking about altering the object, think about creating a transformed copy of the object and passing the copy along.

    If you really truly do need mutable state though, you can use a mutable atom to hold an immutable state:

    (def mut-state (atom []))
    
    (swap! mut-state conj 1)
    
    (println @mut-state)  ; Prints [1]
    

    [] is still immutable here, the new version is just replacing the old version in the mutable atom container.

    Unless you're needing to communicate with UI callbacks or something similar though, you usually don't actually need mutability. Practice using loop/recur and reduce instead.