? The Two Pillars of JavaScript - Pt 2: Functional Programming (archive)

Source originale du contenu

In the very beginnings of the computing revolution, decades before the invention of the microprocessor, a man named Alonzo Church did groundbreaking work in theoretical computer science. You may have heard of his student and collaborator, Alan Turing. Together, they created a theory of computable functions called the Church-Turing Thesis, which described the nature of computable functions.

Two foundational models of computation emerged from that work. The famous turing machine, and lambda calculus.

Today, the turing machine is frequently cited as the baseline of requirements for a modern programming language. A language or processor / VM instruction set is said to be turing complete if it can be used to simulate a universal turing machine. The first famous example of a turing-complete system was lambda calculus, described by Alonzo Church in 1936.

Lambda calculus went on to inspire one of the first high-level programming languages, and the second-oldest high-level language in common use today: Lisp.

Lisp was (and remains) extremely influential and popular in academic circles, but it has also been very popular in productivity applications, particularly those that work on continuous data such as scalable vector graphics. Lisp was originally specified in 1958, and it’s still often embedded in many complex applications. Notably, almost all popular CAD applications support AutoLISP, a dialect of Lisp used in AutoCAD, which (including its many specialized derivatives) is still the most widely used CAD application in the world.

Continuous data: Data that must be measured rather than counted, and could take any value within a range. Contrast with discrete data, which can be counted and accurately indexed. All values come from a specific set of valid values. For instance, the frequencies that can be produced by a violin must be represented by continuous data, because the string can be stopped at any point along the fingerboard. The frequencies that can be produced by a piano are discrete, because each string is tuned to a specific pitch and can’t be stopped at different points depending on the performer’s will. There are usually 88 notes on a piano keyboard. It’s impossible to count the number of notes that could be produced by a violin player, and violin players frequently take advantage of that by sliding the pitch up or down between different notes.

Lisp and its derivatives birthed an entire family of functional programming languages which includes Curry, Haskell, Erlang, Clojure, ML, OCaml, etc…

As popular as many of those languages are, JavaScript has exposed a whole new generation of programmers to functional programming concepts.

Functional programming is a natural fit for JavaScript, because JavaScript offers a few important features: First-class functions, closures, and simple lambda syntax.

JavaScript makes it easy to assign functions to variables, pass them into other functions, return functions from other functions, compose functions, and so on. JavaScript also offers immutable values for primitive types, and features that make it easy to return new objects and arrays rather than manipulate properties of those that are passed in as arguments.

Why is all that important?

Functional programming glossaries contain a large number of large words, but at its core, the essence of FP is really very simple; programs are built mostly with a handful of very small, very reusable, very predictable pure functions.

Pure functions have a few properties that make them extremely reusable and extremely useful for a variety of applications:

Idempotence: Given the same inputs, a pure function will always return the same output, regardless of the number of times the function is called. You may have heard this word used to describe HTTP GET requests. Idempotence is an important feature for building RESTful web services, but it also serves the very useful purpose of separating computation from dependency on both time and order of operations — extremely valuable for parallel & distributed computation (think horizontal scaling).

Because of idempotence, pure functions are also a great fit when you need to operate on continuous data sets. For instance, automated fade-ins and fade-outs in video or audio. Typically they’re designed as linear or logarithmic curves independent of framerates or sample rates, and only applied at the last possible moment to produce discrete data values. In graphics, this is analogous to scalable vector images like SVG, fonts, and Adobe Illustrator files. Key frames or control points are laid out relative to each other or to a timeline, rather than keyed to fixed pixels, frames, or samples.

Because idempotent functions don’t have any dependency on time or sample resolution, it’s possible to treat continuous data as unbounded (virtually infinite) data streams, which allows free scaling of data across time, (sampling rates), pixel resolution, audio volume, and so on.

Free from Side-effects: Pure functions can be safely applied with no side-effects, meaning that they do not mutate any shared state or mutable arguments, and other than their return value, they don’t produce any observable output, including thrown exceptions, triggered events, I/O devices, network, console, display, logs, etc...

Because of the lack of shared state and side-effects, pure functions are much less likely to conflict with each other or cause bugs in unrelated parts of the program.

In other words, for the OOP coding audience, pure functions produce stronger guarantees of encapsulation than objects without function purity, and those guarantees provide many of the same benefits as encapsulation in OOP: The ability to change implementation without impacting the rest of the program, a self-documenting public interface (the function signature), independence from outside code, the ability to move code around freely between files, modules, projects, and so on.

Function purity is FP’s answer to the gorilla/banana problem from OOP:

“The problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.” ~ Joe Armstrong

Obviously, most programs need to produce output, so complex programs usually can’t be composed using only pure functions, but wherever it’s practical, it’s a good idea to make your functions pure.


Working in FP


FP provides several kinds of functions that are extremely reusable. Various implementations will have a different set using different names. This list is mostly pulled from Haskell documentation. Haskell is one of the more popular functional languages, but you’ll find functions similar to these in several popular JavaScript libraries:

List utilities:

Predicates / comparators (test an element, return boolean)

List transformations:

List searches:

List Reducers / folds:

Iterators / Generators / Collectors (infinite lists)

Some of these common utility extras got added to JavaScript with the ECMAScript 5 Array extras. e.g.:

// Using ES6 syntax. () => means function () {}
var foo = [1, 2, 3, 4, 5];
var bar = foo.map( (n) => n + 1 ); // [2, 3, 4, 5, 6]
var baz = bar.filter( (n) => n >=3 && n <=5); // [3,4,5]
var bif = baz.reduce( (n, el) => n += el); // 12


Lists as a Generic Interface


You might notice that most of the functions above are utilities for managing or reasoning about lists. Once you start doing a lot of functional programming, you might start to see virtually everything as a list, an element of a list, a predicate used to test list values, or a transformation based on list values. That thinking lays the foundation for extremely reusable code.

Generics are functions that are capable of working on a range of different data types. In JavaScript, many functions that operate on collections are generic. For example, you can use the same function on both strings and arrays, because a string can be treated as an array of characters.

The process of changing a function that only works with a single type to one that works with many types is called lifting.

The key to all these functions working on every type of data you feed to them is that data is processed in lists which all share the same interface. I discussed lifting a bit towards the end of my Fluent 2014 talk, “Static Types are Overrated” (see below).


How to Stop Micromanaging Everything


In OO and imperative programming, we micromanage everything. We micromanage state, iteration counters, the timing of what happens when using things like event emitters and callbacks. What If I told you that all of that work is completely unnecessary — that you could literally delete entire categories of code from your programs?

You may have heard of functional reactive programming. Here’s the gist: FRP uses functional utilites like map, filter, and reduce to create and process data flows which propogate changes through the system: hence, reactive. When input x changes, output y updates automatically in response.

In OO, you might set up some object (say, a form input), and turn that object into an event emitter, and set up an event listener that jumps through some hoops and maybe produces some output whenever the event is triggered.

In FRP, you instead specify data dependencies in a more declarative fashion, and most of the heavy lifting is offloaded to standard functional utilities so you don’t have to reinvent the wheel over and over again.

Imagine that every list is a stream: An array is a stream of values in element order. A form input can be a stream of values sampled with each change. A button is a stream of clicks.

A stream is just a list expressed over time.

In Rx (reactive extensions) nomenclature, you create observable streams and then process those streams using a set of functional utilities like the ones described above.

Similar to Node streams, observables can feature optional shared backpressure. Like generators, your program can consume observable values at its own pace, as it becomes ready to handle them.

Imagine you want to watch all your social media feeds for a particular hash tag. You could collect a list of lists, merge them in the order they’re received, and then use any combination of the utility functions above to process them.

That might look something like this:

var messages = merge(twitter, fb, gplus, github)
messages.map(normalize) // convert to single message format
.filter(selectHashtag) // cherry pick messages that use the tag
.pipe(process.stdout) // stream output to stdout


Better Async


You may have heard of promises. A promise is an object that provides a standard interface for dealing with values that may or may not be available at the time the promise is used. In other words, a promise wraps a potential value that may be resolved in the future. Typically, a function call will return a promise that may resolve with some future value:

fetchFutureStockPrices().then(becomeAMillionaire);

Well, it doesn’t work exactly like that, because `.then()` will only be called when the stock prices become available, so when `becomeAMillionaire` finally gets run, the “future” stock prices will be the present stock prices.

A promise is basically a stream that only emits a single value (or rejection). Observables can replace promises in your code and provide all of the standard functional utilities you may have grown accustomed to with libraries like Underscore or Lo-Dash.

Now would be a great time to learn more about functional programming, the benefits of function purity, lazy evaluation, and a lot more. I promise you’re going to hear a lot more about these topics over the next few years.

Here we are, coming up on 2015, returning to computer science roots planted in the 1930's so that we can more easily manage the software of future.

Higher Order Learning


I really loved this talk by Jafar Husain, Technical Lead at Netflix. It’s called, “Asynchronous Programming at Netflix” and it was delivered at @Scale 2014.

https://www.youtube.com/watch?v=gawmdhCNy-A

James Coglan gave a good talk that touches on some key topics such as the roles of promises and laziness:

If you’re ready to take FRP for a spin, check out the Reactive Extensions for JavaScript, The Reactive Manifesto, and work through these interactive lessons.

For node-centric streams, check out “How I Want to Write Node: Stream All the Things!” and Highland.js.

For a much more detailed overview of concepts related to reactivity, see Kris Kowal’s excellent “General Theory of Reactivity” (aka gtor).

My talk from Fluent 2014 demonstrates lifting and discusses how dynamic or inferred types can actually be beneficial to functional programming because it makes it trivial to write generic functions.