5 ms·
There's a point-by-point rebuttal in the comments that is also worth looking at: https://gist.github.com/vjeux/cc2c4f83a6b60d69b79057b6ef651b56#gistcomment-230
by mrkgnao 9y ago
There's a point-by-point rebuttal in the comments that is also worth looking at:
https://gist.github.com/vjeux/cc2c4f83a6b60d69b79057b6ef651b56#gistcomment-2302804 https://gist.github.com/vjeux/cc2c4f83a6b60d69b79057b6ef651b...
In my own opinion, I think the author would be better served by sticking with it for a little while longer.
And, no, to head off one common strand of arguments at the pass: no, as a Haskeller and FP enthusiast, I (very strongly) do not think pure, statically-typed, globally-type-inferred (?) programming is perfect or some kind of One True Way. Does it have warts? Of course it does, and don't believe anyone who tries to tell you otherwise.
But the pure FP way of thinking has a lot to offer, and seeing all its idioms through an imperative, C-family-language lens is counterproductive.
A good thing to keep in mind here is the "Principle of Charity": when faced with an unfamiliar spin on something you know how to do in a different way, it's useful not to approach it with the intent of disproving it. Existential Comics has a guide[0] to learning philosophy as an amateur that talks about this.
[0]: http://existentialcomics.com/blog http://existentialcomics.com/blog
- gue5t 9y agoThis rebuttal isn't very good. The lack of canonical names for parameters to data constructors is usually addressed by convention, and can present real problems for reading unfamiliar codebases. There are solutions, but this is a legitimate complaint and saying "OO also has some nameless things" doesn't help. A more helpful response would point at lenses and tools for jumping to datatype definitions, where fields' purposes should be documented. Functional "mutation" tracking is subsumed by dataflow tracking, which is necessary in every programming language. The rebuttal is basically right here. Partial application is occasionally useful, but in languages where it isn't built-in, it's easy to wrap multi-argument functions in closures to curry them by hand. The comparison to overloading is based on visual similarity but has no real relevance to potential difficulty in understanding the meaning of curried function calls. The notes on higher-order function calls and state-passing are okay, but it would be nice to note the ways state-passing can be avoided even in pure code in many functional languages. Lack of methods is a real annoyance, not because the syntax is important, but because methods enable type-dependent dispatch. The right response here is to point at typeclasses and traits, which are how ad-hoc polymorphism is usually done in functional programming languages. In Rust, for example, traits provide methods with the same syntax as "OO" classes. Cons-/linked lists are O(n) access and there are many situations where avoiding significant slowdowns involves roundabout code transformations or "reverse; process; reverse" nonsense. Both random-access mutable containers and immutable functional ones are useful and there are applications where the constant slodown factor due to indirection (often more so than the logarithmic factor) associated with pure data structures is prohibitive. Poo-pooing early return rather than pointing at tools in functional languages (do-notation) for writing similiarly expressive code is ignorant at best, and disingenuous at worst. The problem of H-M error messages bringing up two parts of a program and saying they conflict, rather than pointing at one part of the program and saying it's a mistake, is a well-known drawback. There other points in the design space of type inference and typechecking that may better align with what programmers expect for assigning "blame" to type errors.
- mrkgnao 9y ago> This rebuttal isn't very good. I'm inclined to agree, and I do find your points more "airtight". (I see you've been downvoted; it wasn't me.)
- nerdponx 9y agoI agree with you and I'm not sure why you're being downvoted. Dispatch, especially multiple dispatch, can be really useful when used judiciously.
- l_dopa 9y agoThis is exactly what higher-order functions do, without the ad-hoc OO junk that's slightly different in every language.
- nerdponx 9y agoI'm not talking about higher order functions. I'm just talking about regular old dispatch, like in C++.
- yawaramin 9y agoIn a way C++ is using higher-order functions conceptually too for polymorphic runtime dispatch. Because it uses vtables, which are lists of function pointers--essentially the object doing the dispatch is a HOF which calls one of the functions it has a pointer to. All this is just made explicit, and the boilerplate OOP mechanisms stripped away, when you just directly pass first-class functions around.
- nerdponx 9y agoI guess I just don't understand the connection between first class functions and multiple dispatch. I have a function "add()" that works differently for strings and numbers. How can I replicate that behavior with higher order functions?
- chubot 9y agoEh, I think the OP had more of an argument. Saying something is a "non-argument" is not a useful rebuttal! I completely agree about the lack of names. I based my current language project around Zephyr ASDL [1], a DSL used in Python to describe abstract syntax with the model of algebraic data types. And it does force you to use names, which I very much like. I've looked at a lot of language implementations and algorithms in ML, and the first thing I read is the abstract syntax. And the lack of names always bugs me. Also, the lack of namespacing. In Rust, the enum variants are scoped to the name of the sum type. But in ML you will often see people using explicit prefixes like in C, to provide your own namespacing. (At least, I saw this in the early implementations of the Rust compiler itself in OCaml.) [1] http://www.oilshell.org/blog/tags.html?tag=ASDL#ASDL http://www.oilshell.org/blog/tags.html?tag=ASDL#ASDL
- mrkgnao 9y agoI agree with you on namespacing: I've found myself having to add prefixes myself when working with a bunch of similar datatypes with identically-named constructors (e.g. terms and types in a compiler have many similarities). Saying "non-argument" and ending it there is not useful, but in this case it's shorthand for something along the lines of "not even wrong", and I think that's a valid answer for some (not all!) of the points in the OP. To pick one example: the author gives an example of an eta-reduced function and then says: let fn1 a b c d = ... do something ... let fn2 a b c = fn1 a b c fn2 a b c d > and you're like ...wtf... where the hell does d come from. (Or their criticism of partially-applied functions, which is essentially the same thing.) This is a "non-argument": the only thing that is being made clear here is that the author hasn't spent sufficient time learning the idioms of an unfamiliar style of programming. Yes, horrible partial-application like f = (g .) . ($) . (. (. h)) or whatever is always a bad idea, but saying that writing map f is always worse than map (\x -> f x) is much more likely to be a symptom of unfamiliarity than a deep, cutting critique of functional programming style.
- lgas 9y agoThe whole thing was obviously driven by unfamiliarity and stubbornness in clinging to the familiar but your comparison of "map f" vs "map (\x -> f x)" is not equivalent to his comparison of "array.map(fn)" to "array.map(x => fn(x))". The haskell versions are equivalent but the javascript versions are not due to the airty issues raised in the comments.