DEV Community

Cover image for Why I love learning functional programming

Why I love learning functional programming

Kimmo Sääskilahti on August 01, 2020

This is the first part of a series on my journey in learning functional programming (FP). In this first part, I'd like to share why I spend time on...
Collapse
 
pentacular profile image
pentacular

Why do you consider this to be declarative?

const double = (arr) => 
    arr.filter(v => v % 2 === 0).map(v => v*v);
Enter fullscreen mode Exit fullscreen mode

It looks very much like chained imperative operations to me. :)

Although generally speaking I agree with the drift of what you're saying.

The key quality of functions is that they are time-invariant relationships, which is why there are no effects to track.

Keeping track of effects gets increasingly expensive as the scope of effects increases.

Which is why writing in an algorithmic style can be a good middle-ground -- an algorithm is a procedure implementing a function.

Within an algorithm you can have whatever effects you find convenient, but since it implements a function, the scope of those effects is limited to the body of the procedure, meaning that the cost of proving global correctness does not increase.

Once you understand this, it's should be clear that the rest is just syntax, and you should pick the syntax that's most convenient for the use-case, rather than taking an ideological stance. :)

Collapse
 
cappe987 profile image
Casper

It looks very much like chained imperative operations to me. :)

That code is essentially piping the result of the previous computation into the next function. As Kimmo said, it doesn't modify any state, it's pure, and each function creates a new list that is returned from each function. All functions used in the example are pure. While the dot-syntax looks imperative, it follows the principles of FP in this case.

Here is the exact same code written in F#, but using the pipe instead of dot. It works the same way a pipe in Bash does, takes the result of the previous calculation and passes it to the next. The only real change I made from JS to F# was that I replaced the dots with pipes.

let double arr = 
  arr 
  |> filter (fun v -> v % 2 = 0) 
  |> map (fun v -> v * v)

(The pipes can be all on the same line, like the dots, I just formatted it to be easier to read)

So dot-syntax used correctly can be used to write functional code. As long as the higher order function here is pure and the dot-function itself is pure and returns a new instance of the data.

Collapse
 
pentacular profile image
pentacular

What does expressing an eagerly evaluated imperative operation in a functional style have to do with being declarative programming?

Thread Thread
 
savagepixie profile image
SavagePixie • Edited

Maybe you should look up a quick definition of declarative programming.

The difference between imperative and declarative is not in the operations you use but in the constructs. The example is declarative rather than imperative because the logic is expressed without an explicit control flow.

Thread Thread
 
pentacular profile image
pentacular

a.b(c).d(e) has no explicit flow control?

What an interesting idea ...

It looks very explicitly defined by the semantics of ecmascript to me.

But perhaps I'm missing something -- could you explain more about how a.b(c).d(e), etc, has no explicit flow control?

Thread Thread
 
savagepixie profile image
SavagePixie • Edited

Certainly. If we oversimplify it, in computer science control flow refers to statements such as ifs or fors.

As you can see, in a.b(c).d(e) there are no such statements. So there is no explicit imperative structure to control the flow of execution. There is only one execution branch explicitly stated.

By contrast, something like the following code would have explicit control flow:


if (a) {
  for (let i = 0; i < 12; i++) b(i)
} else {
  switch (c) {
    case 2:
      d(e)
      break;
    ...
  }
}

As you can see, the difference is that there are control flow starements to control the flow of execution. If you look at Wikipedia's page for control flow it even lists the control flow structures that exist.

Collapse
 
johnkazer profile image
John Kazer

Other than struggling (in JavaScript) to keep track of types and data structure I enjoy functional programming because it is more relaxing. No worries about unintended consequence and more control about what's going on due to function return values instead of ceding control to mysterious procedures.

Collapse
 
juanfrank77 profile image
Juan F Gonzalez

Hey, join the club!
I don't like learning FP because it reminds me of Maths...
I like learning it because it makes me think different than OOP & imperative programming that was shoved down my throat back in college.

Also, it makes programs simpler and easier to test and it just looks beautiful.

Collapse
 
cappe987 profile image
Casper
square :: Int -> Int
square = foldl (*) 1 . replicate 2

isEven :: Int -> Bool
isEven = (== 0) . (`mod` 2)

It's not so elegant if you make it all point-free though. Point-free notation can make some code easier to read. But if you take it too far it just becomes worse. In the case of your double function, the parameter was last in the chain and nowhere else so it makes sense to remove it.

Collapse
 
ksaaskil profile image
Kimmo Sääskilahti

Thanks for the comment! I agree with everything you said :)

Collapse
 
eljayadobe profile image
Eljay-Adobe

I've been dabbling in functional programming for a couple years, for fun.

What I see as the big win of functional programming over object-oriented programming is immutability, recursion, succinct syntax, pattern matching, higher-order functions, code-as-data, separation of behavior from data, and referential transparency.

When I see functional programming done in C++, it makes me sad. The syntax is awkward, immutability in C++ isn't there, recursion is a non-starter, pattern matching can be very awkwardly mimicked with lambdas and traversing a selection vector, higher-order functions are anemic, code-as-data is extremely limited, separation of behavior from data requires strict discipline, as does referential transparency.

The biggest cons against functional programming is that it tends to have a much bigger memory footprint over C++, say anecdotally on average about x4. (Your mileage may vary, based on your particular program.)

I've looked at the various how to do functional programming in language XYZ (where XYZ is one of: C++, Swift, JavaScript, Rust, Scala, Lisp, Scheme, and others), and I'm disappointed in those languages compared to functional programming first functional programming languages like OCaml, Haskell, F#, or Elm.

Yes, I include Lisp and Scheme in the "bad list". I haven't tried Clojure which adds functional programming to Lisp. Lisp programmers who haven't used a functional programming language may have trouble grokking that sentence, since Lisp has the interesting position of being a general general purpose programmer's programming language. Lots of power there! And unlike C++ token substitution macros made by The Devil™, Lisp macros are magically awesome.

Applying functional programming techniques in non-functional programming languages doesn't seem like a big win, to me. Other developers in the language will probably consider the functional programming techniques to be non-idiomatic and out of place.