Pissed off about functional programming (2005)
perlmonks.org
perlmonks.org
In myth 3 he seems to mix words of description and the words describing logical equivalence. "even though equals('three',3) is true, length('three') does not equal length(3)" - while this is correct, it only seems to say anything about the myth because of the function name. Try this instead "even though foobar('three',3) is true, length('three') does not equal length(3)" - does this really prove anything? You could never substitute "three" with 3 at any point in the first place.
Specifically he hid a "$lut{ $str }" in equals(). It's not that 3 can be substituted with "three" - it can be substituted with "$lut{"three"}".
In myth 4 he seems to play a similar trick of talking about the variable names. Sure, perl allows you to create a new block with a new variable of the same name. I'm not sure what does that have to do with functional programming as a whole. It's just the language implementation that allowed you to play this trick - referring to a new thing by a name you used before.
I'm struck by A) the arrogance of such a statement, and B) how completely unsurprised I am by it. The fact that the rest of the article is nitpicky stuff of no real consequence, with a heaping helping of strawmen and goal-post moving, only supports this. This is a person who fears being wrong and is defending their turf.
For the proverbial "you", because this is an endemic problem in our industry:
It's not necessary to defend turf, both if you are right and if you are wrong. Obviously, if you are wrong, it saves you time to have someone else figure it out for you and then you can adjust accordingly (you're going to adjust once proven wrong, right?).
But if, in some weird happenstance that has yet to be demonstrated in history, you're right, you have expended no effort to defend your position. Sometimes, you have to let people learn their lessons the hard way. Let the youngsters run in their enthusiasm and trip and fall on their face sometimes. It's the only way to build a healthy fear of novelty-for-the-sake-of-novelty, and instill some critical thinking as a matter of course.
It's not necessary to be "right" all the time. Just because there are people in the world who are "Doing it Right" versus "Doing it Wrong", doesn't mean you have an ordained duty to inform them.
Because I'm really getting sick and tired of being asked "why didn't you use <whatever I like> for that <whatever you wrote>?"
I reject that last bit, that these things Y lead to a useless language though I do think the proof was far from the pudding 9 years ago.
So what exactly was he missing? When I was programming in FP languages, the process didn't seem all that different from other languages, except generally more cumbersome.
So true. Especially if the domain is not "naturally" functional - say an HTML5 game. I am sure brilliant programmers will come up with a functional model even in such cases, but I don't think that is how most people would think. I am more fond of languages like JavaScript, which has features associated with functional programming (higher-order functions, closures) but feels as natural to me as C.
you're doing it wrong.
FP is not actually the One Right Answer for every problem.
http://research.microsoft.com/apps/pubs/default.aspx?id=2112...
In some sense, despite the author's assertion that FP is mind-expanding and a great learning experience, I believe he still had a lot to learn. Mostly that most of these techniques and benefits are workable, but they take a genuine change in perspective (such as outlawing ambient state entirely).
My own exposure didn't show me that much that was new/different in ways that were practically useful (and no, it wasn't writing FORTRAN or BASIC or Pascal in a different language).
Mind you, I have, for example used (mostly) immutable data structures and generally write in what many would probably consider a somewhat functional style. I also like HO mechanisms, but all of these are not at all exclusive to FP languages.
So as I wrote, I am looking for concrete examples.
fix f = f (fix f) -- ought to take forever, right?
fact' rec n = if n == 0 then 1 else n * rec (n-1)
Here `fix` appears to set up an infinite computation, but applying it as `fix fact'` produces a terminating function, the factorial.Finally, simultaneity in LC doesn't much hold you back from using concurrency because you have two avenues: (1) model your concurrency abstractly within LC and then execute it specially and (2) have concurrency applied implicitly with optional hinting.
Both of these are a little non-obvious, but extremely workable.
Myth 2: Summarized as: Turing Completeness holds, perhaps? It seems to be attacking a certain strawman, but in doing so depends a lot upon some definition of functional as being a totally different form of computation. It's hard to even concretely respond to this abstract notion, but I'll try with two points
1. LC and TMs are not equivalent on higher order computation. While any function Int -> Int can be equally represented in each, functions like (In -> Int) -> Int are different. In LC you cannot examine the input function (with tradeoffs and benefits) and in TM you can (with tradeoffs and benefits). That's totally theoretical, though since you can model higher order computation as a function Int -> Int for most practical purposes.
2. Turing Completeness is not an end goal. Some languages today even challenge whether you want TC to hold all of the time (or only sometimes) and push right up the border of TC from below. I think they provide example that taking TC as your ultimate arbiter of language comparison is shortsighted.
Myth 3: Referentially transparent is not a property of a language or of a (poorly defined) style of language. It's a property of an analysis of a language, the language's definition/semantics/statics. I've discussed this on this page in another comment concerning C.
The short of it is, however, that RT depends upon what you're willing to analyze as a frame of reference and for any language you can pick choices of frames which provide RT or those which break it.
The author's choice of mechanism to demonstrate RT breakage is pretending like variables in Perl are references and then showing that mutability and dynamic scoping breaks them. Then he even tries to pretend like universal use of dictionaries models reference and breaks that.
These are strawman arguments, deliberately pushing models outside of their theoretical value and then showing that they no longer have theoretical value.
Ultimately, this culminates in an unjustified argument that "referential transparency isn't all that desirable" due to "incredibly tight constraints" being untenable.
As a concrete counterexample, I give you pretty much the semantics of Haskell which has a ton of practical code written in it and referentially transparent variable semantics by default. It goes much further than his ideas do and achieves it in such a way where the programmer does not have the ability to break the model.
(For instance, if Haskell let you modify its own running memory and fiddle around with arguments on the stack then you'd certainly be able to use those facilities to break RT... but you can't.)
Myth 4: This is total strawman—he asserts that FP does not allow "assignment", defines assignment as he chooses, and then shows that his own model of assignment can be modeled without assignment. He does this by embedding an imperative language in a "functional" style in Perl and then observing the effects of running that embedded language.
Then he shows... something utterly unrelated to referential transparency but claims that it's the same. Since you asked for concrete, here's his example in Haskell
data Op = Inc | Dec | Zero
counter :: Int -> [Op] -> [Int]
counter n [] = []
counter n (op:ops) = case op of
Inc -> n + 1 : counter (n+1) ops
Dec -> n - 1 : counter (n-1) ops
Zero -> 0 : counter 0 ops
-- modified to return only the last value
counter' n ops = last (counter n ops)
x = [Inc, Inc, Inc]
y = [Inc, Inc, Inc]
test1 = counter' x == counter' x
test2 = counter' x == counter' y
test3 = counter' x == counter' (x ++ [Inc]) -- what?
---Ultimately, while he does spend a little time talking about how reference works—and he's not completely wrong in his definitions—his application of this idea to "FP" (whatever he defines it as) is full of logical fallacies, unbacked assertions of impossibility, and... ultimately just totally flawed arguments.
It turns out you can have much of what he's asking for—even in Perl if you like. But to do so you must intentionally restrict yourself from using certain parts of your language. If you move the goal posts on your restriction over and over then, yes, you can discover a lot of weird counterexamples to your own broken models.
Isn't there a rather famous phrase, 'You can write Fortran in any language'?
I kinda skimmed over the OP, admittedly, and the lambda the ultimate responses; I find myself far more on the side of the lambda the ultimate responses (that this guy is largely strawmanning/misunderstanding FP), but that's still immaterial to me, as I'm not really interested in academic arguments.
I've been programming in Erlang (which though not lazy nor pure still embraces an FP approach), and I've found that after embracing the languages idioms, structuring my code and my thinking to take advantage of the language, I'm far more productive than I was in any imperative language (and became so in less time than I had spent in those imperative languages). And things like concurrency, while still complex, have become comparatively trivial than the crap I was having to deal with in those imperative languages.
Now, that may be a combination of other factors (a REPL, more concise language, fewer teammates writing better code), and it's anecdotal, same as your response, but ultimately I think personal experience is going to trump academic argument every time.
No matter how much someone extols the benefits of FP, if you don't experience them you won't be swayed, and similarly no matter how academic an argument against FP someone tries to make, having experienced benefit from using a functional language, academic arguments are of little interest to me, and I'm curious to try more functional languages.
All I'd say is, to your experience, examine whether you were simply trying to bend the language to fit your existing models and approaches, or if you were willing and able to replace your models and approaches with those the language required (i.e., in Haskell, not merely "Okay, I need to do a side effect here, so time to write 'do'", but "Hmm, I have a side effect here...is this the right place for it? What other side effects do I have? Can I minimize where they're being called, so I have comparatively few functions that require the io monad?" etc). If it was the latter, fair enough. My experience was different; both are valid.
There's a difference between being able to imagine such a semantics and having that semantics be in common and widespread use or having it be useful to explain functions of the language or common techniques.
The nice thing about, e.g., Haskell is that these referentially transparent semantic models are really just shoved into your face. Purity makes it hard to ignore referential transparency. Monads make it incredibly clear when you have regions of code which aren't referentially transparent.
So, much like the whole contentious "vacuous type system" arguments from a day ago---these analyses exist naturally for practically any language you can think of but their value varies a lot depending on whether or not they are natural.
I said it that way to emphasize the argument that existence of a referentially transparent semantics isn't enough, though. You need extant and useful.
Like he says, the technique/viewpoint is already known in FP. But in certain languages you have more control over it than others.
The call of a side-effecting function of course it has a well-defined value and I don't think there's anybody knowledgeable enough in FP to deny that. Problem is - there's data missing in the representation of side-effecting operations. For example, take a statement like this:
x = x + y
As a matter of fact, this makes no sense mathematically, because there's data missing from the above representation, as it really means this (where i, j and k are moments in time): x(i) = x(i - j) + y(i - k)
So time is actually an implicit parameter here that you've got no control over. The above representation makes even more sense when studying the architecture of CPUs. And as a side-effect, the old value of "x" gets lost and so if anybody else holds a reference to "x", then that reference will point to an entirely different value, an event that can happen at arbitrary points in the future, so you could say that the whole world changes after an operation like that. Since concurrency is often brought into the picture, I think it's fairly easy to see how this can create problems in terms of our capability to reason about the logic we read or write.Also, if we think about side-effecting components (say, mutable objects), the functional behavior of such a component ends up depending on its history, history that's in no way explicit. Variables are buckets or places. Immutable values are facts that can be compared. E.g. 2 references to mutable things cannot be considered equal, unless they point to exactly the same memory location, otherwise equality (as defined in the programming languages we are using) is broken and a constant source of gotchas.
That link you posted argues that the way we are using the term "referential transparency" today for programming languages is different from its original meaning, but I'm arguing that the original meaning is not relevant for programming languages. There's no point in arguing that a side-effecting function call in C does have a well-defined "value". Yes, the output of a side-effecting function can be considered a value, but you can't treat it as a value.
Of course, we can always argue semantics.
It's my guess that he's talking about non-pure FPLs like Lisps and MLs, which today don't seem nearly as FP as Haskell, Idris, Agda, Coq - the langs that are now carrying the FP torch.
>Now.. with those definitions in place, you can see how the relationship between simultaneity and lazy evaluation isn't quite as simple as it appears at first glance. It's theoretically possible that the 'f()' or 'x' might change between the lazy evaluation of step one and the lazy evaluation of step three million. Trying to prevent that is what we programmers call a 'hard' problem.
It only makes sense if you don't consider Haskell to be the most immediate example for lazy, purely functional programming.
> It's theoretically possible that the 'f()' or 'x' might change between the lazy evaluation of step one and the lazy evaluation of step three million. Trying to prevent that is what we programmers call a 'hard' problem.
and the examples he gives in Myth 3 & Myth 4 where he reassigns `x` to mean something else. Both of these can be avoided had x been immutable.
I agree with the conclusion in Myth 2. I didn't understand the latter half of Myth 3, so can't comment on that.
Am I missing something wrt my assumption of immutable values solving most of the issues?
http://www.infoq.com/presentations/java-performance
Scroll to 41:05
My favorite is "Functional programming is becoming more relevant because...multi-core" so your 100x slower functional code would get a 4x speed up and only be 25x slower?
Working back toward greater sharing from the share-nothing viewpoint is a great place to throw effort.
Hickey, Datomic?
[1] Whatever 'the' concurrency problem may be and not implying that I agree that the Clojure people believe they can solve it or intend to solve it
This implies immutability, because pieces of code, or functions that mutate variables / objects / data-structures are NOT referentially transparent.
> I think fifty years of lisp would like to have a few words with this fellow
LISP is not really a family of FP programming languages, even though LISP in general does apply concepts from lambda calculus and does have everything needed for FP. LISP in general is more like a multipurpose swiss army knife. For example the style people use to develop in Common Lisp is often very far away from FP. There are exceptions of course, like Clojure, which comes with immutable data-structures by default, strived to make uncontrolled mutation more painful and the culture around it does encourage FP.
x = 3 = y
thus `x` and `y` are both just new names for the same identical value `3`. Since both variables reference the same thing then we ought to be able to substitute x for y whenever we like.Of course, in Perl `x` isn't really a variable but instead a name referencing a "slot" which can be mutated. That mutation breaks the reference and makes `x` hold more meaning than merely a "reference to 3". So then you cannot substitute it for `y` any longer.
I think his example code snippet is a little obtuse, though.
You can't re-bind values in the lambda calculus, so no, they never will change. This is why languages like haskell are immutable.
His complaint #3 also relies on the false assumption of a mutable language.
Complaint #4 was solved by monads, quite a while ago.