If I have a function that takes a java collection and returns the count, it has no referential transparency because the collection is mutable, not necessarily b/c of how it's operating on its arguments.
Of course this is one of the things I love about Clojure and Rich Hickey's use of immutability as the default behavior (as often as possible) - much [more] of the Clojure I write has referential transparency, hardly any of the Java I wrote did.
Thanks!
Kyle
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.
(defn make-adder [x y] (fn [z] (+ x y z)))
((make-adder 1 2) 3)
-----VS-------------
(def ^{:dynamic} x 1) (def ^{:dynamic} y 2)
(defn make-adder [] (fn [z] (+ x y z)))
((make-adder) 3)
In the first example, the closure returned is still a function of the arguments passed to make-adder, and it is easy to reason about. In the second example, the closure returned relies on the dynamic value of x and y at run time. I agree that if you close over a reference to a mutable value you can also break referential transparency, but that's the exception and not the rule with clojure since it is immutable by default. With dynamic vars you are almost guaranteed to break referential transparency.
I've used implicit params in my toy compiler code. While it somewhat simplified the structure of the code, I could have easily written the program without them.
[1] http://www.haskell.org/ghc/docs/latest/html/users_guide/othe...
use 5.014;
use warnings;
our ($x, $y) = (2, 2);
sub sum_of_squares {
my ($a, $b) = @_;
($a * $a) + ($b * $b);
}
sub sum_of_squares_for_x_and_y { sum_of_squares $x, $y }
say sum_of_squares_for_x_and_y; # => 8
{
local ($x, $y) = (10, 5);
no warnings 'redefine';
local *sum_of_squares = sub {
my ($a, $b) = @_;
$a / $b;
};
say sum_of_squares_for_x_and_y; # => 2
}
say sum_of_squares_for_x_and_y; # => 8
PS. I seem to be making a lot of HN comments on dynamic scoping lately :)http://news.ycombinator.com/item?id=3446384 | http://news.ycombinator.com/item?id=3423631