DEV Community

Discussion on: Example: Imperative vs. Functional

 
gypsydave5 profile image
David Wickes

Whew - some fine responses!

And yes, if you encapsulate both versions in their own function, to their consumers they do the same thing.

Let's do this!

here's the one with imperative guts:

function imp(friends, itemToSearch) {
  let resultImperative = [];
  for (friend of friends) {
    if (friend.drinks.includes(itemToSearch)) {
      resultImperative.push(friend.name);
    }
  }

  return resultImperative
}

and here's one with functional guts

function fun(friends, itemToSearch) {
  return friends
    .filter(friend => friend.drinks.includes(itemToSearch))
    .map(friend => friend.name);
}

Now we've got some structure into this (just to keep Dijkstra happy) we can perform a fair comparison.

Both of these functions are 'pure'.1 They are both stateless, returning the same value for the same set of arguments. They do not mutate state - they have no effect upon any value outside of the function.

to their consumers they do the same thing

They don't only 'do' the same thing, they are indistinguishable. Other than the imperative one is faster. You do the maths.

The point is, the loop breaks referential transparency, which means that you have opened up the possibility for a class of errors that misuse it.

The loop does break referential transparency. But the solution as a whole doesn't. Do you know what else breaks referential transparency:

  .filter(friend => friend.drinks.includes(itemToSearch))

Yup, that there lambda don't get itemToSearch as an argument... I guess it's not functional. But the solution as a whole is.

Anyway, obligatory xkcd comic:

White Hat questions Cueball's faith in functional programming. Cueball responds saying, "Tail recursion is its own reward."

Functional programming combines the flexibility and power of abstract mathematics with the intuitive clarity of abstract mathematics.


  1. schoolofhaskell.com/school/startin... 

Thread Thread
 
nicholasnbg profile image
Nick Gray

Nice, thanks for the great discussion, and I agree with your points, as always nothing is black and white, both paradigms have benfits, and yes FP can be daunting, I'm very lucky to have landed my dev job at a company with a strong FP community so have a lot of people around me to learn from.

One question, in regards to the filter function not being referentially transparent, could you make it so like this?:

function fun(friends, itemToSearch) {
  const friendFilter = (friend) => friend.drinks.includes(itemToSearch)
  return friends
    .filter(friend => friendFilter(friend))
    .map(friend => friend.name);
}
Thread Thread
 
gypsydave5 profile image
David Wickes • Edited

Classy! But It still involves a non-referentially transparent function.

const friendFilter = (friend) => friend.drinks.includes(itemToSearch)

itemToSearch is still closed over by the lambda (i.e. it's not one of its arguments).

Before I get into this - I really wouldn't do this in real life! It's fine that the lambda closes over itemToSearch. I'm only doing this because it's fun and I enjoy FP :D.

But since you asked... one way of handling this - if you really, really only wanted pure functions everywhere - would be to pass itemToSearch as an argument and return the friendFilter function, essentially currying the function:

function fun(friends, itemToSearch) {
  const friendFilter = (item) => (friend) => friend.drinks.includes(item)
  const filterItem = friendFilter(itemToSearch)

  return friends
    .filter(friend => filterItem(friend))
    .map(friend => friend.name);
}

Lambda and currying make a great way to add data into your functions as and when its needed.

small refactor:

function fun(friends, itemToSearch) {
  const friendFilter = (item) => (friend) => friend.drinks.includes(item)
  const predicate = friendFilter(itemToSearch)

  return friends
    .filter(predicate)
    .map(friend => friend.name);
}

stupid why-oh-why refactor:

const fun = (friends, itemToSearch) => friends
    .filter((item => friend => friend.drinks.includes(item))(itemToSearch))
    .map(friend => friend.name);

Ridiculous, please-make-it-stop refactor

const fun = (friends, itemToSearch) => 
  (predicate => friends.filter(predicate).map(friend => friend.name))
  ((item => friend => friend.drinks.includes(item))(itemToSearch))

If you enjoy this madness, might I recommend reading some books about the Scheme programming language.

Thread Thread
 
nicholasnbg profile image
Nick Gray

Haha nice, I actually work with Scala day to day and a lot of code ends up looking similar to the second last example (with types).

Thanks again mate :)

Thread Thread
 
miku86 profile image
miku86 • Edited

Thanks for sharing all your insights (all of you), I will have a closer look at them.