I'm taking down this post. I just posted this as a side comment to explain a sentence on my latest blog post. This wasn't meant to be #1 on HN to start a huge war on functional programming... The thoughts are not well formed enough to have a huge audience. Sorry for all the people reading this. And please, don't dig through the history...
-
-
Save vjeux/cc2c4f83a6b60d69b79057b6ef651b56 to your computer and use it in GitHub Desktop.
@vjeux You have a very poor attitude towards criticism, and your knowledge of CSS says absolutely nothing about your knowledge of functional programming, as demonstrated by this very post. I am sincerely trying to be helpful when I say that I think you would be wise to reconsider how you react to what people have to say about this post. You'd do very well to learn from this experience, take a second look at functional programming (perhaps with another language?) and try to approach it from a more open-minded perspective. I assure you that the experience will benefit you tremendously.
@zsck: the fact that I do not believe that point-free programming is a good idea and believe that having explicit arguments written and passed around is better doesn't mean that I do not understand functional programming. This exact argument is in the first paragraph of the wikipedia article about it: "The lack of argument naming gives point-free style a reputation of being unnecessarily obscure, hence the epithet "pointless style.""
I also do not believe that functional programming is "a completely different paradigm". I view it more as a bunch of programming patterns that are lumped together under the functional programming umbrella because they work well together. In this post, I tried to show that there were equivalent ways to write some of them with some, arguably weak, arguments explaining why the fp way was not ideal.
Functional programming has a lot of great aspects and learning all those patterns has certainly enlightened me and made me a better programmer. Now it doesn't mean that I don't find some aspects of it suboptimal and that I can't try to find a different set of patterns that are working better for my use cases and talk about it :)
Functional Programming is a completely different paradigm from imperative and Object-Oriented Programming. Almost all of the complaints raised here with the exception of the naming problem and OCaml syntax stem directly from trying to wedge FP concepts into imperative ones. Trying to fit a square in a circle, so to speak.
You wouldn't, as an English speaker, try to understand Mandarin by comparing it to English. At times like this, one has to embrace a new way of thinking in order to understand not only the value, but the motivation for that way of thinking.
Okay; so what is this different way of thinking? In what way is FP a square and in what way are things like readability circles? I don't see things like readability as an "OOP" thing, it's just good software engineering. If you want to make this argument, you need to explain either why a) readability doesn't matter, or b) FP doesn't actually hamper readability.
If I can, I'd like to address each point one by one.
Lack of names
This is kind of a non-argument, really - naming is a problem regardless of functional/imperative programming and many things in typical OO languages have the same problem of namelessness.
Hard to track "mutations"
Again, a non-argument to anyone who's dealt with functional programming in any significant capacity. Most programs written in a functional style take the form of a sort of pipeline of transformations, and it's fairly easy to see in each piece how the data would be changed. Very rarely do you actually have to track down these "mutations" as they never happen unless they're explicitly asked for somewhere along that pipeline - simply following the pipeline will lead you to the specifics of the transformations that are happening. Compare this to an imperative/OO style where tracking these mutations is an absolute necessity, since you can never tell for certain whether a variable was inadvertantly changed by some piece of library code, for example.
Partial evaluation
While I can see your point here, I think perhaps you've missed the real purpose of partial applications. Yes, they can be abused. Yes, they can be cryptic when abused. But for the most part, partial application is a functional programmer's best friend - almost nothing we do could be done without them, or at least not half as easily. And here once again we have a case of "pipeline solves all", if you will; if you can't tell where an argument to a function comes from, it's trivial to follow it up the pipeline and find the missing argument, since you only have to compare the return values and arities of each function in the chain. Compare this to an imperative/OO style where... wait, imperative languages don't typically have partial application. That means that most of the time if you can't tell where an argument came from in an imperative language, you may be looking at the wrong overload, which isn't always trivial to track down, especially when multiple inheritance comes into play.
Higher order functions
I think you're confusing two different concepts here. The example you give shows two different instances of higher order functions, rather than one of higher order functions and one that is not. All the second example does is wrap the function in a lambda (which is really just a shortcut syntax for an anonymous function), so it's still using higher order functions. Yes, in general it's often better to be more explicit about the flow of arguments. However, this is not an issue with higher order functions at all, but rather with coding conventions and the readability issues that can sometimes spring from them.
Passing values around
Again, I think this is a misinterpretation of what the real problem is. This is, however, one of the failings of many functional programmers IMHO. There is nothing wrong with local mutation. The problems come when you have global mutation, or mutation across the boundaries of a function. That said, often introducing an extra variable and avoiding mutations can actually be clearer even in purely local situations. For example, take the example of a factorial
function; which of the following do you think is clearer about the exact intentions of the function?
// Imperative style
function factorial(n) {
let fact = n
for(n; n > 0; --n) {
fact *= n
}
return fact
}
// Functional style
function factorial(n) {
if(n == 1) return 1
else return n * factorial(n - 1)
}
The former requires you to step through the logic of each and every loop iteration to fully understand the function. The latter, on the other hand, matches the mathematical definition of a factorial to a T: If n
is 1, give back 1, otherwise give back n
times the factorial of n - 1
. Simple, easy, effective, and doesn't require simulating the loop in your head to understand exactly how it works. The performance of the latter could be improved by using tail recursion and an accumulator value, but that doesn't significantly change the point I'm making. Once you understand recursion, it's honestly a lot more clean and intuitive than imperative-style loops in a lot of cases.
In your particular examples, the reduce
statement is admittedly a little cryptic if you don't know how reduce
works; however, considering that map
, filter
, and reduce
are three of the most basic building blocks of a functional programming language, that becomes a non-argument to anyone who's worked with FP in any significant capacity.
Lack of methods
While this is an issue that comes down to what languages you use, it's less of an issue than you might think. A lot of languages such as Scala and F# offer dot-notation style in specific contexts (namespacing, interop with imperative languages, etc.).
True, dot-notation style is great - I personally prefer it myself for a lot of things. On the other hand, a big part of the idea behind functional programming is to unwrap the data. Methods aren't discarded as a concept haphazardly for no reason; they're discarded because other concepts fill the gap just fine. Well-written functional code is often as generic as possible - that is, it's less about what something is (objects, inheritance, methods, etc.) and all about what something does (lists, arrays, and sets can often be treated in exactly the same way, so it doesn't matter exactly what they are as long as they respond the same). This makes namespacing a perfect alternative to methods - it's a simple switch from "Hello, world!".reverse()
to String.reverse "Hello, world!"
, and allows for programming things in a much more generic and reusable way.
Lists
I can't attest to the performance benefits or drawbacks, but from a development perspective, lists and arrays can be treated almost exactly the same in most languages, so for reading and understanding the code at least there's no significant difference here.
Lack of early return
This is one of my personal pet peeves as well, but while it would often be nice to have, it's rarely really necessary. Most problems can be solved with branching in the same way they could be solved with early returns, and a lot of times you can simplify the branching a lot more than imperative-style programming tends to get you in the habit of. Pattern matching is a great help in this area as well. In many contexts, early returns are simply a way of flattening out branching anyways, and since most functional languages aren't so afraid of indentation as their imperative counterparts, it just becomes a difference in style. For those problems that can't be translated from early returns to branching, there's usually another way to approach the problem that solves it more cleanly and succinctly anyway, in my experience.
Global inference error messages
This is a common problem with type inference in general. However, most of the time if you actually read the error message instead of just noting that "hey, there's an error there", it's fairly easy to track down the type error to the preceding function (though I can't attest to how well OCaml in particular does with this as I've never used it). Again we see a case of "pipeline solves all"; a functional programming style using primarily pure functions makes things really, really easy to track through each step of the changes and find errors like this that could hide for hours in an imperative style.
OCaml Syntax, Refs: I can't speak to this as I've never used OCaml.
It would seem that a lot of the points you bring up are either based on misconceptions about how functional programming works, on the admittedly rough transition from OO/imperative languages to functional ones, or simply on mixing up the terms for a couple of different things. Hopefully I've helped clear some of that up - I'd love to hear what you think, though =)
@AlecBenzer I think you were missing the point of that quote - by far the majority of the problems he raised become non-issues to start with if you have a decent knowledge of functional programming, or are problems regardless of imperative/functional style. The main point of the comment you quoted, was to say (correctly) that many of the issues he raised stem entirely from approaching functional programming with an imperative mindset, rather than approaching functional programming with a functional mindset.
In a lot of cases, more succinct code is more readable code. FP actually wins in this case, as once you're beyond the initial learning curve, you begin to find that recursion, pattern matching, map/filter/reduce, and so on are actually more succinct, readable, and powerful ways to express the same concepts.
You're entirely right that readability, good naming, and so on are not "OOP things". However, that was entirely not the point - most of the issues people have with reading and understanding functional code come not from the code itself, but from viewing the code the same way you would imperative code. The underlying principles and concepts of the two styles are different - while they are similar in a lot of ways, it's nearly impossible to fully understand functional code if you try to look at it purely from an imperative standpoint. An open mind, a willingness to learn, and an understanding that the basic building blocks of the two styles are different; all of those are necessary to truly grokking with FP when transitioning from OOP.
Pure FP makes code look/act/feel like math. Some believe this is more concise with guarantees, equational reasoning, and a universe of well-studied, ready to use, composable pieces. Others believe that programming is better off reading like a work of fiction, explaining each process in (mostly) english with data modeled as "real world" objects.
With the latter mindset, the above arguments seem obvious to me. But i think it's worth applying the criticisms to mathematics in general if we're thinking in the first mindset.
@tripl3dogdare thanks for the thorough response!
Lambdas
<MyComponent onClick={(value)=> setNewValue(value)} />
<MyComponent onClick={(value)=> setNewValue(value)} />
Without Lambdas
<MyComponent onClick={this.incrementValue} />
<MyComponent onClick={this.decrementValue} />
less functions with lambdas but less readable too
@vjeux, I think you are just misleading beginners in FP. As you can see all who know FP are telling you that you just need to practice more on FP to understand the benefits. This article is same thing like a beginner on plane will tell everyone that planes sucks and let's drive on cars only, because planes are not understandable at all (I don't want to compare planes with cars now, that was just a comparation ). Please just practice more, or not, but writing something like this being blind, is not professional. There are a lot of really good articles about FP downsides, even made by FP gurus, and there are very well described technical problems (hashmaps, trees, sorting).
by far the majority of the problems he raised become non-issues to start with if you have a decent knowledge of functional programming
But the comment didn't point out how this was true, it just asserted that it was. (Unlike your own previous comment)
Like, you can use this argument to defend anything. "X isn't bad, you're just seeing X from a non-X perspective, so it seems bad". Ok.... but you need to explain how from some other sane perspective, X is good. Otherwise you're not ruling out the possibility that X is just a bad perspective to look at things from.
I will join the crowd of people who say that this is an uninformed opinion by someone who does not know sufficiently well what they are talking about. Certain things take time to understand, and they cannot be explained easily in comments to a gist. People take whole courses in programming to get used to a new way of programming, be it functional, imperative or quantum computing. There's nothing wrong about not knowing functional programming, but I would respectfully advise that the next time around you phrase your objections as questions to the FP community, rather than uninformed criticism. You will get a much better reponse, and you might learn something instead of spend time arguing.
(My background: I do research in programming languages, and I teach theory of programming languages.)
But you missed the point of Functional Programming Paradigm. There were no constructive criticizm just some kind of personal opinion of what you do not like with FP in general. It is even potentially dangerous for newcomers and people that read it "from distance". I could go into detail with everything that I think didn't do the justice to FP, but I see no point in doing so.
Partial reply to the following point from your lists paragraph
you cannot easily go backwards
Here is a suggestion for a parent linked tree in a functional language (F#)
I consider the above point important because it has been mentioned even in the first paragraph of a recent article and because I believe that parent linked trees are really needed in real world e.g. to get the path of node. I guess this use case was missing.
"passing values around (reduce)". so the alternative is the go style where everything is explicit. in practice the first problem is that it can be tedious to write. reduce/map are geared for increasing velocity because you can do the same with less. the second problem is that the explicitness can encourage bad practises. imagine mutating the first variable at the end of a long sequence of for loops... I'd argue that that can be just as bad if not worse.
the bottom line is that writing good code takes practice because it's an art.
I've been programming OCaml for years and I too miss early returns. However there is a good solution. There was a long discussion about this on the OCaml mailing list a few months back, and there are various with_return
modules. My contribution to the art is this implementation.
at least now I know it must be pretty easy to get a job at fb, in case i need a backup
@jonsterling you seem like a wonderful person to work with.
There is a lot of really good reasons why functional programming isn't good for every use-case. It would be meaningless to enumerate them here as they're obvious if you take a bit of time to look for them. This gist (unfortunately) doesn't make any good arguments against using the paradigm :(
Worth reading the history on this one
He is right, this is exactly how I felt when I first switched to functional programming.
Nevertheless, with time, you start to adapt yourself and get it and FP starts to make sense.
Although, I still dislike the way FP people thinks brevity increase readability... I mean look at APL, it is short.
Furthermore the examples they pick are always too simplistic to make a point.
Finally, the performance of FP is something I am very interested in and I was never able to found something about it.
I am also worried about the cache unfriendliness of lists but everybody seems to forget about processor cache when working on distributed systems, on a other side Scala does allow you to use arrays instead of lists and I am mainly using arrays anyway.
Well, I'm pretty sure you have not much idea of what you are talking about. What you call implicit function is point-free programming, a common and declarative style, and the core of functional programming languages can be composed via combinators written in these forms. Early returns without an SSA/CPS conversion are clearly breaking the paradigm.
Partial application is just implicit in languages with unary functions (like lambda-calculus) and is one of the best features to work with composition, a core feature of functional programming that helps a lot. There is a lot of things that make me sad about functional programming, but definitely are not the paradigm itself, as you are pointing here.