I have been reading about binding but did not really understand what makes it different from let. Do you have any example of use case where using binding is more correct than a let call?
This concept I think does not exist in many other languages. So it is a bit unexpected when learning Clojure, that such variables are possible.
Global variables that can be changed locally. And if you leave the local scope, they go back to the original value.
In fact, on top of my mind, I cannot remember any other OO language that I used before that contains such type of concept. Seems to be something interesting, but I still cannot imagine real use cases where this is the "correct" approach for a problem. Maybe, some type of configuration overriding, where some values of configuration are based on vars? Btw, I am checking "Clojure for the Brave and True" because I remember of an introduction for bindings, and later I'll take a look at "Clojure High Performance Programming".
> I still cannot imagine real use cases where this is the "correct" approach for a problem.
print, for example, and all its friends.
How else would you replicate all the print functionality, given that some functions that your code uses might use print themselves with specific overrides that they care about.
It's not just configuration override, it's local override - applied to a particular scope, having no effect outside of that scope.
It’s essentially dynamic scoping. https://en.wikipedia.org/wiki/Scope_(computer_programming)#Dynamic_scope
let is about local bindings. When you do (let [a 10] ...), a is a local constant with value 10, nothing else.
binding is about vars - things that exist at the level of a namespace, not locally. It basically temporarily puts a value into a globally available location.
(def ^:dynamic language :en)
(defn say-hello []
(get {:en "hi!"
:sv "hej!"}
language))
(say-hello)
;=> "hi!"
(defn in-swedish [do-stuff]
(binding [language :sv]
(do-stuff)))
(in-swedish say-hello)
;=> "hej!"
(let [language :sv]
(say-hello))
;=> "hi!"Ah, and the answer the explicit question explicitly - you use binding when you use dynamic vars that you have to temporarily override. And you use such vars when you have no better mechanism of temporarily changing how something works by default.
Most often, it's possible and even better to do without them - by passing explicit config maps around. But sometimes it's either impossible or incredibly cumbersome. Like all the *print-...* vars - imagine that you'd have to add a map to each and every call of print and all the related functions. It's just not possible when you want to alter how some existing code prints stuff without altering that code.
So, can I consider vars something like "global variables"?
Kinda, but more flexible since you can dynamically create or remove them and also temporarily override them in a thread-safe way. But I'd suggest viewing them as "global constants that, if absolutely necessary, could be changed". Otherwise you'd be in the world of stateful place-oriented programming world of pain.
> in a thread-safe way There is a huge caveat to this statement which is that vars use thread locals. They are not the best tool for sharing mutable data across multiple threads.