- What is Functional Programming?
- How Functional Programming Differs From Other Paradigms
- Advantages and Disadvantages of Functional Style
- Interaction of Functional and Object-Oriented Styles
- Restricted sense, FP means programming without mutable variables, assignments, loops and other imperative control structures
- Wider sense, FP means focusing on the functions
- Particularly, functions can be values that are produced, consumed, and composed.
Functions in a FP languages are first-class citizens. This means:
- they can be defined anywhere, including inside other function
- like any other values, they can be passed as parameters to functions and returned as results
- as for other values, there exist a set of operators to compose functions
- Pure Lisp, XSLT, XPath, XQuery, FP
- Lisp, Scheme, Racket, Clojure
- SML, F#
- Haskell (full language)
OO focuses on the differences in the data, while FP concentrates on consistent data structures.
- Data and the operations upon it are tightly coupled
- Objects hide their implementation of operations from other objects via their interfaces
- The central model for abstraction is the data itself
- The central activity is composing new objects and extending existing objects by adding new methods to them
- Data is only loosely coupled to functions
- Functions hide their implementation, and the language’s abstractions speak to functions and the way they are combined or expressed
- The central model for abstraction is the function, not the data structure.
The central activity is writing new functions
One main distinguishing characteristics of functional programming languages is that they describe what they want done, and not how to do it. OO, inside its methods, still uses mostly imperative techniques.
- modifying mutable variables
- using assignment
- and control structures such as if-then-else, loops, break, continue, return.
The most common informal way to understand imperative programs is as instruction sequences for a Von Neumann computer
Processor <—- bus —-> Memory
- concentrate on functions
- avoid mutations (mutation can bestroy useful laws)
- have powerful ways to abstract and compose functions
- First-class functions
- Lambdas/Anonymous Functions with closures
- Compact, even terse, functions
- Mostly stateless processing
- Side-effect-free functions calls
- Performant recursion through tail call optimization
- Pattern matching (Haskell, Erlang)
- Lazy Evaluation (Miranda, Haskell)
- Homoiconicity (mostly LISP-like languages?)
This calculates the odds of choosing the correct
n numbers out of the
- recursive functions are often much more elegant than their iterative cousins.
- Unfortunately, they often don’t perform as well. All the overhead of creating stack contexts for function calls tends to add up. But certain kinds of recursive calls can be easily optimized.
Note that the recursive call in odds1 is the last statement in its branch of the function. If this is true for all recursive calls, then the function is tail-recursive, and the compiler can replace the entire set of nested calls with simple JUMP operations.
- Underscore, Lo-Dash
Example will be a Task List application, fetching something like the following data from the server:
The goal will be a function that accepts a
member parameter, then fetches the data from the server (or from some application cache), chooses the tasks for that member that are not complete, returns their ids, priorities, titles, and dues dates, sorted by due date.
- asynchronous, we’ll hook everything together with promises.
- ignore all error-checking
The contents of the functions are much the same; it’s the way they are organized that varies.
The process for the remainder of this talk will be to convert this code into concise, readable, functional code, one block at a time, explaining some of the basic building blocks of functional programming as we go. First up is this little function:
So the obvious question, then, is, what is the prop function?
Our then call needs a function, so curry must be doing something interesting with this function, which should return an object propery, to instead returning a new function. So we need to take a detour to discuss curry a bit.
Currying is the process of converting functions that take multiple arguments into ones that, when supplied fewer arguments, return new functions that accept the remaining ones.
Now that we understand curry, we can see that a manually curried version of this function might look like this:
And that means that our new get(‘tasks’) is equivalent to
But we’ve done something more important too: We’ve moved the focus from iteration and updating the state of a local collection to the real point of this block: choosing the tasks with the proper member property.
One of the most important features of functional programming is that it makes it easy to shift focus in this manner.
The next block is similar, except that instead of using filter, we will use reject, which behaves exactly the same except that it chooses those members of the list that don’t match the predicate. We replace this code:
A reasonable question would be why with didn’t do this instead, which would work equally well:
The reason is that the similarity between these two blocks will offer us a chance to refactor our code into something still more descriptive:
Both of these functions accept an object and return a boolean that describes whether a particular property of the object has a given value. Perhaps a good name for a function that generates such functions would be propEq.
This works, and we could leave it there, but we’re going to take another detour into a popular style of functional programming known as points-free coding.
With the functions add (which adds two numbers) and reduce (which runs the supplied function against an accumulator and each element of the list, feeding the result of each call into the next one and returning the final result), we can easily define a sum function like this:
Because of the automatic currying, though, the following is entirely equivalent:
This is the points-free style, defining functions without ever making direct reference to their arguments.
Tacit [point-free] programming is a programming paradigm in which a function definition does not include information regarding its arguments, using combinators and function composition (but not ?-abstraction) instead of variables.
There are plenty of reasons to like it, but the most important one might just be the simplicity. There is a great deal to be said for elegant, readable code.
Can we redefine the following in a points-free style?
Here’s a version that is closer to points-free, removing the direct reference to obj:
Huh? What? compose? eq?
eq is easy: like all good functions of multiple parameters, it’s curried, and it simply reports whether its two arguments are equal. So eq(val) is a function which reports whether its parameter has the same value as does val. But now we need to discuss compose.
In mathematics f ∘ g (pronounced “f composed with g”) is the function that given x, returns f(g(x)).
So if we follow the mathematical model compose(add1, square)(x) should equal add1(square(x)).
Note that Ramda also defines pipe, which does much the same thing, but runs the functions in the opposite order. So pipe(add1, square)(x) equals square(add1(x)). Both styles have their uses.
So now this definition makes sense:
Note the switch from compose to pipe.
This gives us a further way to clean it up, and make it entirely points-free, using a useful feature of Ramda we haven’t seen implemented in other libraries, which we call (for now) callWith. Used like callWith(fn, transformer1, … transformerN), this returns a function which accepts N parameters, feeds them to the respective transformers, and then calls fn using the results of all these. This gives us the final version of propEq:
Still, all these explanations aside, it was only a minute or two of work to do this refactoring and arrive at a fairly simple version of propEq. We would plug it back in like this:
The next block we wanted to update looked like this:
You’re a smart bunch, right? I probably don’t even have to explain what pick does, right?
One of the most fundamental functions used in FP is map, which is used to convert one list into a related one by running the same function against each member.
There isn’t much more to say about map, but it’s important to point out that this function and reduce, which we mentioned briefly earlier, are among the most important functional programming tools around.
We used map like this:
The magic of currying is again at work here:
- pick accepts a list of properties and an object and return a partial clone, copying those properties from the original. Since we just pass in the properties, this curried function returns a new function that accepts an object and returns that partial clone.
- map accepts a function and a list an applies the function to the list. But because it’s curried, and because we supply only the function generated by the curried pick, this one also returns a new function which will accept a list and create these partial clones of each element in the list.
- This function is passed to then, which will simply pass along its parameter (whenever that becomes ready) to our function and “return” the result of running our function against it. (We simply know because of the way prior calls have been built that this will be a list of tasks.)
The last segment we wanted to convert looked like this:
Sorts the list according to a key generated by the supplied function.
Again, we take advantage of the fact that our important functions are curried, and use prop(‘dueDate’). This creates a function that, fed one of our task objects, returns its due date. We can feed this into sortBy to get the following:
While this is not a lot less code than the original:
it clearly is a savings. However, much more importantly, the code is all clearly aimed at our problem.
We’ve seen what are probably the most important functions in functional programming:
There’s one that to some might be conspicuous by it’s absence:
Maybe it is not actually appropriate to functional programming, as its main purpose is to help you achieve side-effects. But many libraries do include one.
- Elegance and simplicity
- Easier decomposition of problems
- Code more closely tied to the problem domain
And through these, we can also achieve
- Straightforward unit testing
- Easier debugging
- Simple concurrency