Instantly share code, notes, and snippets.

@vjeux /x.md
Last active Aug 31, 2018

Embed
What would you like to do?
Ocaml / functional programming

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...

@timbuckley

This comment has been minimized.

Show comment
Hide comment
@timbuckley

timbuckley Dec 22, 2017

Re: High order functions

array.map(fn)          // #1
array.map(x => fn(x))  // #2

I don't see what the problem is with #1 above, as long as the function is well-named (and if we ignore the problem of multi-arity functions in JS). The second version doesn't help you understand the return value either.

timbuckley commented Dec 22, 2017

Re: High order functions

array.map(fn)          // #1
array.map(x => fn(x))  // #2

I don't see what the problem is with #1 above, as long as the function is well-named (and if we ignore the problem of multi-arity functions in JS). The second version doesn't help you understand the return value either.

@naholyr

This comment has been minimized.

Show comment
Hide comment
@naholyr

naholyr Dec 22, 2017

@timbuckley About the map, there is actually a big difference in JS, as array.map(fn) is equivalent to array.map((x, i, a) => fn(x, i, a)). I've learnt to write explicit arguments passing more because of that than for legibility tbh ;)

But I agree with you, usually your array is named properly, and naming x will just be redundant :

users.map(stringify)

// Do I really have to write that?
users.map(user => stringify(user))

with real life example it seems less obvious than with dummy array.map(fn) which means nothing.

naholyr commented Dec 22, 2017

@timbuckley About the map, there is actually a big difference in JS, as array.map(fn) is equivalent to array.map((x, i, a) => fn(x, i, a)). I've learnt to write explicit arguments passing more because of that than for legibility tbh ;)

But I agree with you, usually your array is named properly, and naming x will just be redundant :

users.map(stringify)

// Do I really have to write that?
users.map(user => stringify(user))

with real life example it seems less obvious than with dummy array.map(fn) which means nothing.

@AndrewIngram

This comment has been minimized.

Show comment
Hide comment
@AndrewIngram

AndrewIngram Dec 22, 2017

@timbuckley, depending on language, it may not be safe to consider them as equivalent, in weird JS:

const a = [1, 2, 3];

a.map(console.log);
// 1 0 [1, 2, 3]
// 2 1 [1, 2, 3]
// 3 2 [1, 2, 3]

a.map(x => console.log(x))
// 1
// 2
// 3

You have to be really careful about the arity of callback functions

AndrewIngram commented Dec 22, 2017

@timbuckley, depending on language, it may not be safe to consider them as equivalent, in weird JS:

const a = [1, 2, 3];

a.map(console.log);
// 1 0 [1, 2, 3]
// 2 1 [1, 2, 3]
// 3 2 [1, 2, 3]

a.map(x => console.log(x))
// 1
// 2
// 3

You have to be really careful about the arity of callback functions

@DannyMeister

This comment has been minimized.

Show comment
Hide comment
@DannyMeister

DannyMeister Dec 22, 2017

I doubt the complaint has anything to do with understanding the return type, but more to do with not being able to easily understand that high ordered functions are being used verses what someone from say Java would assume, which is that an instance or value was being passed to a function.

I think that confusion is really only a common issue if a predominantly non-functional code base contains such a line of code, or if an imperative/OO programmer is still in the initiation phase into functional programming.

Now that I am well initiated, I assume high order functions are the norm. The syntax is not confusing to me, though I will admit to often needing to hover an F# function to see the type info to validate my assumptions. Mostly confusion is avoided by a naming convention. Verbs are usually function names, nouns are usually data.

DannyMeister commented Dec 22, 2017

I doubt the complaint has anything to do with understanding the return type, but more to do with not being able to easily understand that high ordered functions are being used verses what someone from say Java would assume, which is that an instance or value was being passed to a function.

I think that confusion is really only a common issue if a predominantly non-functional code base contains such a line of code, or if an imperative/OO programmer is still in the initiation phase into functional programming.

Now that I am well initiated, I assume high order functions are the norm. The syntax is not confusing to me, though I will admit to often needing to hover an F# function to see the type info to validate my assumptions. Mostly confusion is avoided by a naming convention. Verbs are usually function names, nouns are usually data.

@timbuckley

This comment has been minimized.

Show comment
Hide comment
@timbuckley

timbuckley Dec 22, 2017

To be clear, I think the original point is more about readability/explicitness. I know map in JS passes other arguments like index, and so you have to be careful with the arity of the callback. But in more statically typed languages like OCaml/Haskell, you avoid that entire issue, so it comes back to readability than accidentally using a function with unintentional arity.

timbuckley commented Dec 22, 2017

To be clear, I think the original point is more about readability/explicitness. I know map in JS passes other arguments like index, and so you have to be careful with the arity of the callback. But in more statically typed languages like OCaml/Haskell, you avoid that entire issue, so it comes back to readability than accidentally using a function with unintentional arity.

@sompylasar

This comment has been minimized.

Show comment
Hide comment
@sompylasar

sompylasar Dec 22, 2017

RE: tracking mutations and higher order functions, try debugging this lodash-fp based code:

https://github.com/prettier/prettier-atom/blob/fa8e118/src/formatOnSave/shouldFormatOnSave.js#L48-L60

Even with great names it's hard to trace in a debugger where a value flowing through this pure functional code is wrong for a particular item of a large array that's being mapped over with splitting and conditional joining of the data flow.

The incompatible arity or types of arguments may bite, too, in a language without first-class validation of them.

sompylasar commented Dec 22, 2017

RE: tracking mutations and higher order functions, try debugging this lodash-fp based code:

https://github.com/prettier/prettier-atom/blob/fa8e118/src/formatOnSave/shouldFormatOnSave.js#L48-L60

Even with great names it's hard to trace in a debugger where a value flowing through this pure functional code is wrong for a particular item of a large array that's being mapped over with splitting and conditional joining of the data flow.

The incompatible arity or types of arguments may bite, too, in a language without first-class validation of them.

@sompylasar

This comment has been minimized.

Show comment
Hide comment
@sompylasar

sompylasar Dec 22, 2017

With higher-order functions, by the way, in a decent size codebase good luck finding every actual runtime call site of a function when this function gets passed around a few times under different names.

sompylasar commented Dec 22, 2017

With higher-order functions, by the way, in a decent size codebase good luck finding every actual runtime call site of a function when this function gets passed around a few times under different names.

@sompylasar

This comment has been minimized.

Show comment
Hide comment
@sompylasar

sompylasar Dec 22, 2017

RE: partial evaluation, I think it starts to make sense if you rewrite your example into what is actually going on:

let fn1 a b c d = ... do something ...
let fn2 a b c /* d is ommitted but applied partially */ = fn1 a b c
fn2 a b c d
// let fn1 a b c d = ... do something ...
let fn1 = function(a) { 
  return function(b) { 
    return function(c) { 
      return function(d) { 
        return dosomething
      } } } }

// let fn2 a b c /* d is ommitted but applied partially */ = fn1 a b c
let fn2 = function(a) { 
    return function(b) { 
      return function(c) { 
        return fn1(a)(b)(c)
      } } }

// fn2 a b c d
fn2(a)(b)(c)(d)  // -> dosomething

// fn2 a b c
fn2(a)(b)(c)  // -> function(d) { return dosomething }

sompylasar commented Dec 22, 2017

RE: partial evaluation, I think it starts to make sense if you rewrite your example into what is actually going on:

let fn1 a b c d = ... do something ...
let fn2 a b c /* d is ommitted but applied partially */ = fn1 a b c
fn2 a b c d
// let fn1 a b c d = ... do something ...
let fn1 = function(a) { 
  return function(b) { 
    return function(c) { 
      return function(d) { 
        return dosomething
      } } } }

// let fn2 a b c /* d is ommitted but applied partially */ = fn1 a b c
let fn2 = function(a) { 
    return function(b) { 
      return function(c) { 
        return fn1(a)(b)(c)
      } } }

// fn2 a b c d
fn2(a)(b)(c)(d)  // -> dosomething

// fn2 a b c
fn2(a)(b)(c)  // -> function(d) { return dosomething }
@zsck

This comment has been minimized.

Show comment
Hide comment
@zsck

zsck Dec 22, 2017

I don't think @vjeux understands that 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. There are certainly cases where OOP or imperative programming may be better suited to expressing solutions to certain kinds of problems, but it doesn't make sense to complain about FP's "lack of some feature from a non-functional language" for exactly the same reason it doesn't make sense to complain about Mandarin for "not having English features" like, say, spaces for word delimiters.

zsck commented Dec 22, 2017

I don't think @vjeux understands that 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. There are certainly cases where OOP or imperative programming may be better suited to expressing solutions to certain kinds of problems, but it doesn't make sense to complain about FP's "lack of some feature from a non-functional language" for exactly the same reason it doesn't make sense to complain about Mandarin for "not having English features" like, say, spaces for word delimiters.

@vjeux

This comment has been minimized.

Show comment
Hide comment
@vjeux

vjeux Dec 22, 2017

@zsck: to your point, a lot of people don't think that I understand how CSS works ;) https://speakerdeck.com/vjeux/react-css-in-js

Owner

vjeux commented Dec 22, 2017

@zsck: to your point, a lot of people don't think that I understand how CSS works ;) https://speakerdeck.com/vjeux/react-css-in-js

@phaazon

This comment has been minimized.

Show comment
Hide comment
@phaazon

phaazon Dec 22, 2017

I'm sorry, but most of those advices are pretty bad. You turn advantages from FP and turn them into drawbacks because you don't seem to fully understand why they're advantages in the first place. For instance, free point style is neat because you don't have to introduce meaningless symbols / variables and can just focus on transformations.

I strongly think this posy's OP lacks hindsight and experience in FP, whatever OCaml, Haskell or any FPL.

phaazon commented Dec 22, 2017

I'm sorry, but most of those advices are pretty bad. You turn advantages from FP and turn them into drawbacks because you don't seem to fully understand why they're advantages in the first place. For instance, free point style is neat because you don't have to introduce meaningless symbols / variables and can just focus on transformations.

I strongly think this posy's OP lacks hindsight and experience in FP, whatever OCaml, Haskell or any FPL.

@phaazon

This comment has been minimized.

Show comment
Hide comment
@phaazon

phaazon Dec 22, 2017

Also, add CPS and you have your “early returns”. But as with my previous comment, there's a good reason we encourage people not to use early return if possible. And you don't seem to know why. It's a pity.

phaazon commented Dec 22, 2017

Also, add CPS and you have your “early returns”. But as with my previous comment, there's a good reason we encourage people not to use early return if possible. And you don't seem to know why. It's a pity.

@haskellcamargo

This comment has been minimized.

Show comment
Hide comment
@haskellcamargo

haskellcamargo Dec 22, 2017

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.

haskellcamargo commented Dec 22, 2017

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.

@zsck

This comment has been minimized.

Show comment
Hide comment
@zsck

zsck Dec 22, 2017

@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 commented Dec 22, 2017

@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.

@vjeux

This comment has been minimized.

Show comment
Hide comment
@vjeux

vjeux Dec 22, 2017

@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 :)

Owner

vjeux commented Dec 22, 2017

@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 :)

@AlecBenzer

This comment has been minimized.

Show comment
Hide comment
@AlecBenzer

AlecBenzer Dec 22, 2017

@zsck:

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.

AlecBenzer commented Dec 22, 2017

@zsck:

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.

@tripl3dogdare

This comment has been minimized.

Show comment
Hide comment
@tripl3dogdare

tripl3dogdare Dec 22, 2017

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 =)

tripl3dogdare commented Dec 22, 2017

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 =)

@tripl3dogdare

This comment has been minimized.

Show comment
Hide comment
@tripl3dogdare

tripl3dogdare Dec 22, 2017

@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.

tripl3dogdare commented Dec 22, 2017

@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.

@DrBoolean

This comment has been minimized.

Show comment
Hide comment
@DrBoolean

DrBoolean Dec 23, 2017

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.

DrBoolean commented Dec 23, 2017

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.

@vjeux

This comment has been minimized.

Show comment
Hide comment
@vjeux

vjeux Dec 23, 2017

@tripl3dogdare thanks for the thorough response!

Owner

vjeux commented Dec 23, 2017

@tripl3dogdare thanks for the thorough response!

@oojr

This comment has been minimized.

Show comment
Hide comment
@oojr

oojr Dec 23, 2017

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

oojr commented Dec 23, 2017

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

@GheorgheP

This comment has been minimized.

Show comment
Hide comment
@GheorgheP

GheorgheP Dec 23, 2017

@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).

GheorgheP commented Dec 23, 2017

@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).

@AlecBenzer

This comment has been minimized.

Show comment
Hide comment
@AlecBenzer

AlecBenzer Dec 23, 2017

@tripl3dogdare

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.

AlecBenzer commented Dec 23, 2017

@tripl3dogdare

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.

@andrejbauer

This comment has been minimized.

Show comment
Hide comment
@andrejbauer

andrejbauer Dec 23, 2017

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.)

andrejbauer commented Dec 23, 2017

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.)

@defphil

This comment has been minimized.

Show comment
Hide comment
@defphil

defphil Dec 23, 2017

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.

defphil commented Dec 23, 2017

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.

@giuliohome

This comment has been minimized.

Show comment
Hide comment
@giuliohome

giuliohome Dec 23, 2017

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.

giuliohome commented Dec 23, 2017

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.

@frankandrobot

This comment has been minimized.

Show comment
Hide comment
@frankandrobot

frankandrobot Dec 23, 2017

"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.

frankandrobot commented Dec 23, 2017

"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.

@rwmjones

This comment has been minimized.

Show comment
Hide comment
@rwmjones

rwmjones Dec 23, 2017

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.

rwmjones commented Dec 23, 2017

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.

@jonsterling

This comment has been minimized.

Show comment
Hide comment
@jonsterling

jonsterling Dec 23, 2017

at least now I know it must be pretty easy to get a job at fb, in case i need a backup

jonsterling commented Dec 23, 2017

at least now I know it must be pretty easy to get a job at fb, in case i need a backup

@btnwtn

This comment has been minimized.

Show comment
Hide comment
@btnwtn

btnwtn Dec 23, 2017

@jonsterling you seem like a wonderful person to work with.

btnwtn commented Dec 23, 2017

@jonsterling you seem like a wonderful person to work with.

@opvasger

This comment has been minimized.

Show comment
Hide comment
@opvasger

opvasger Dec 23, 2017

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 :(

opvasger commented Dec 23, 2017

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 :(

@goofballLogic

This comment has been minimized.

Show comment
Hide comment
@goofballLogic

goofballLogic Dec 23, 2017

Worth reading the history on this one

goofballLogic commented Dec 23, 2017

Worth reading the history on this one

@Delaunay

This comment has been minimized.

Show comment
Hide comment
@Delaunay

Delaunay Dec 24, 2017

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.

Delaunay commented Dec 24, 2017

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment