|
|
|
|
|
by wonderzombie
4905 days ago
|
|
It seems to me that what you've demonstrated is that side effects are possible in Clojure, esp. with sufficiently obfuscated (read: un-idiomatic) code. But you're working too hard anyway; Clojure explicitly admits mutability already, through various concurrency pieces like atoms and refs. That said, if any admission of mutability is sufficient to disqualify a language from claiming to encourage or support referential transparency, then Haskell fails the test, too. unsafePerformIO is a trivial example. I'm sure if you were determined to introduce nondeterminism, you could find a lot more. But that's not the point, is it? Maybe there's a way to demonstrate your point, but what you've shown here doesn't involve homoiconicity or macros at all. |
|
Anyway, I tried learning enough clojure to prove that routine metaprogramming would often be not referentially transparent.
First, note from the wikipedia page that "referentially transparent" essentially means that the same function with the same arguments will always produce the same result, and that you can call it more (throwing away the result) or fewer (replacing calls with the result) times without affecting the meaning of the program.
So, mutating variables (assignment) violates it, as does reading mutable variables other than the arguments. So, using "def" on a variable that already has a value is not referentially transparent.
But let me show you something closer to what I had in mind when I said that referential tranparency and metaprogramming are essentially inconsistent.
Take the simple macro:
First, I'll show that the argument that it takes must be the code itself, rather than the result of evaluating the code. The "or" macro before made it hard to tell whether it was taking the code as arguments or the result of evaluating the code as arguments. But with the macro above, it's easy to see: If we are trying to show that swapargs is referentially transparent, we assume that it will return the same result given the same arguments. Because (mod 7 5) = (mod 10 8), then we know it must not be taking the result as an argument (because the result of the mod is 2 in both cases, but the result of swapargs is different). So it's taking the code itself.Next, we show that give the same code-as-an-argument, it may return different results in different contexts. That's easy to show:
Now, we can't replace the call to swapargs with its result, because it changes depending on the values of "a" and "b" at the time, even though the argument is always the same code "(mod a b)".So, this kind of advanced metaprogramming doesn't seem compatible with referential transparency. Perhaps some subsets are, but I don't even think the C macro system could be supported in a referentially-transparent way.
I also think that kind of metaprogramming tends to defeat many kinds of static analysis, such as advanced type systems. I'm less sure of that one, but for practical purposes now it seems to be true.
So, I think lisp and haskell are close to local maximums for their particular philosophies, but neither one is any kind of epitome of programming or "more powerful" than the other.
Personally, I think lisp-style metaprogramming is very cool, and I am happy I spent a few minutes trying out clojure. However, I don't think it is solving a problem that is very important to me at a practical level. I am trying to learn haskell because it is trying to solve the kinds of problems that I actually have -- mainly software engineering problems (greater confidence in code, more readability and maintainability). Not sure whether it will help solve those problems for me, but they are trying very hard to do so, and make some pretty compelling arguments.