Hacker News new | ask | show | jobs
by lmkg 5271 days ago
Dynamically bound values can be thought of as invisible arguments, that are passed through every function up the call stack. A referentially-transparent function only depends on its explicit arguments, and will always return the same value when passed the same arguments. A function that depends on a dynamically-bound variable may return different values when called with the same arguments.

Example (Common Lisp syntax, not actually tested):

  (defvar z 0)
   
  (defun example (x y)
    (declare (special z))
    (+ x y z))
  
  (example 1 2)
  
  ==> 3
  
  (let ((z 10))
    (example 1 2))
  
  => 13
In this sample, the function example is not referentially transparent, because it yields different results when passed the same argument. Note that this happens without using closures or mutation. example does not close over the value of z in any way because it is dynamically scoped[1]. There is no mutation because z is re-bound, which is conceptually different from mutation. It's effectively creating a new binding with the same name and different scope; a function called within that scope will look for the value by name, and find the new binding. The original binding is untouched outside of this new scope.

[1] This is tautological; The term "closure" is defined to refer to lexical scoping, and was invented to describe it[2].

[2] Actually, now that I'm writing this, it occurred to me that your confusion entirely stems from subtleties in the definitions of lexical closure and bindings. A closure is not any function that refers to symbols outside its body. The term only refers to functions that use lexical scoping to do so, and therefore need to "close over" their surrounding data and carry it around with them. Functions referencing dynamically scoped variables do not need to carry their data around, because they look up the call stack every time.

To your point, it is "referring to symbols outside the function" that breaks referential transparency, but dynamic bindings do so in an orthogonal fashion from closures, and unlike closures do not require mutation to do so.