Home >Web Front-end >JS Tutorial >An Introduction to Reasonably Pure Functional Programming
When learning to program you’re first introduced to procedural programming; this is where you control a machine by feeding it a sequential list of commands. After you have an understanding of a few language fundamentals like variables, assignment, functions and objects you can cobble together a program that achieves what you set out for it do – and you feel like an absolute wizard.
The process of becoming a better programmer is all about gaining a greater ability to control the programs you write and finding the simplest solution that’s both correct and the most readable. As you become a better programmer you’ll write smaller functions, achieve better re-use of your code, write tests for your code and you’ll gain confidence that the programs you write will continue to do as you intend. No one enjoys finding and fixing bugs in code, so becoming a better programmer is also about avoiding certain things that are error-prone. Learning what to avoid comes through experience or heeding the advice of those more experienced, like Douglas Crockford famously explains in JavaScript: The Good Parts.
Functional programming gives us ways to lower the complexity of our programs by reducing them into their simplest forms: functions that behave like pure mathematical functions. Learning the principles of functional programming is a great addition to your skill set and will help you write simpler programs with fewer bugs.
The key concepts of functional programming are pure functions, immutable values, composition and taming side-effects.
A pure function is a function that, given the same input, will always return the same output and does not have any observable side effect.
<span>// pure </span><span>function add(a<span>, b</span>) { </span> <span>return a + b; </span><span>} </span>
This function is pure. It doesn’t depend on or change any state outside of the function and it will always return the same output value for the same input.
<span>// impure </span><span>var minimum = 21; </span><span>var checkAge = function(age) { </span> <span>return age >= minimum; // if minimum is changed we're cactus </span><span>}; </span>
This function is impure as it relies on external mutable state outside of the function.
If we move this variable inside of the function it becomes pure and we can be certain that our function will correctly check our age every time.
<span>// pure </span><span>var checkAge = function(age) { </span> <span>var minimum = 21; </span> <span>return age >= minimum; </span><span>}; </span>
Pure functions have no side-effects. Here are a few important ones to keep in mind:
You need to be aware of Mutator methods on Arrays and Objects which change the underling objects, an example of this is the difference between Array’s splice and slice methods.
<span>// impure, splice mutates the array </span><span>var firstThree = function(arr) { </span> <span>return arr.splice(0,3); // arr may never be the same again </span><span>}; </span> <span>// pure, slice returns a new array </span><span>var firstThree = function(arr) { </span> <span>return arr.slice(0,3); </span><span>}; </span>
If we avoid mutating methods on objects passed to our functions our program becomes easier to reason about, we can reasonably expect our functions not to be switching things out from under us.
<span>let items = ['a','b','c']; </span><span>let newItems = pure(items); </span><span>// I expect items to be ['a','b','c'] </span>
Pure functions have a few benefits over their impure counterparts:
Because the results of pure functions are cacheable we can memoize them so expensive operations are only performed the first time the functions are called. For example, memoizing the results of searching a large index would yield big performance improvements on re-runs.
Reducing our programs down to pure functions can drastically reduce the complexity of our programs. However, our functional programs can also end up requiring Rain Man’s assistance to comprehend if we push functional abstraction too far.
<span>import _ from 'ramda'; </span><span>import $ from 'jquery'; </span> <span>var Impure = { </span> <span>getJSON: _.curry(function(callback<span>, url</span>) { </span> $<span>.getJSON(url, callback); </span> <span>}), </span> <span>setHtml: _.curry(function(sel<span>, html</span>) { </span> <span>$(sel).html(html); </span> <span>}) </span><span>}; </span> <span>var img = function (url) { </span> <span>return $('<img />', { src: url }); </span><span>}; </span> <span>var url = function (t) { </span> <span>return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' + </span> t <span>+ '&format=json&jsoncallback=?'; </span><span>}; </span> <span>var mediaUrl = _.compose(_.prop('m'), _.prop('media')); </span><span>var mediaToImg = _.compose(img, mediaUrl); </span><span>var images = _.compose(_.map(mediaToImg), _.prop('items')); </span><span>var renderImages = _.compose(Impure.setHtml("body"), images); </span><span>var app = _.compose(Impure.getJSON(renderImages), url); </span><span>app("cats"); </span>
Take a minute to digest the code above.
Unless you have a background in functional programming these abstractions (curry, excessive use of compose and prop) are really difficult to follow, as is the flow of execution. The code below is easier to understand and to modify, it also much more clearly describes the program than the purely functional approach above and it’s less code.
<span>// pure </span><span>function add(a<span>, b</span>) { </span> <span>return a + b; </span><span>} </span>
Or, this alternative API using abstractions like fetch and Promise helps us clarify the meaning of our asynchronous actions even further.
<span>// impure </span><span>var minimum = 21; </span><span>var checkAge = function(age) { </span> <span>return age >= minimum; // if minimum is changed we're cactus </span><span>}; </span>
Note: fetch and Promise are upcoming standards so they require polyfills to use today.
The Ajax request and the DOM operations are never going to be pure but we could make a pure function out of the rest, mapping the response JSON to an array of images – let’s excuse the dependence on jQuery for now.
<span>// pure </span><span>var checkAge = function(age) { </span> <span>var minimum = 21; </span> <span>return age >= minimum; </span><span>}; </span>
Our function is just doing two things now:
The “functional” way to do this is to create separate functions for those two tasks and we can use compose to pass the response of one function into the other.
<span>// impure, splice mutates the array </span><span>var firstThree = function(arr) { </span> <span>return arr.splice(0,3); // arr may never be the same again </span><span>}; </span> <span>// pure, slice returns a new array </span><span>var firstThree = function(arr) { </span> <span>return arr.slice(0,3); </span><span>}; </span>
compose returns a function that is the composition of a list of functions, each consuming the return value of the function that follows.
Here’s what compose is doing, passing the response of urls into our images function.
<span>let items = ['a','b','c']; </span><span>let newItems = pure(items); </span><span>// I expect items to be ['a','b','c'] </span>
It helps to read the arguments to compose from right to left to understand the direction of data flow.
By reducing our program down to pure functions it gives us a greater ability to reuse them in the future, they are much simpler to test and they are self documenting. The downside is that when used excessively (like in the first example) these functional abstractions can make things more complex which is certainly not what we want. The most important question to ask when refactoring code though is this:
Is the code easier to read and understand?
Now, I’m not trying to attack functional programming at all. Every developer should make a concerted effort to learn the fundamental functions that let you abstract common patterns in programming into much more concise declarative code, or as Marijn Haverbeke puts it..
A programmer armed with a repertoire of fundamental functions and, more importantly, the knowledge on how to use them, is much more effective than one who starts from scratch. – Eloquent JavaScript, Marijn Haverbeke
Here is a list of essential functions that every JavaScript developer should learn and master. It’s also a great way to brush up on your JavaScript skills to write each of these functions from scratch.
Arrays
Functions
Let’s look at some practical steps we can take to improve the code below using functional programming concepts.
<span>// pure </span><span>function add(a<span>, b</span>) { </span> <span>return a + b; </span><span>} </span>
Reduce functions dependence on shared state
This may sounds obvious and trivial but I still write functions that access and modify a lot of state outside of themselves, this makes them harder to test and more prone to error.
<span>// impure </span><span>var minimum = 21; </span><span>var checkAge = function(age) { </span> <span>return age >= minimum; // if minimum is changed we're cactus </span><span>}; </span>
Use more readable language abstractions like forEach to iterate
<span>// pure </span><span>var checkAge = function(age) { </span> <span>var minimum = 21; </span> <span>return age >= minimum; </span><span>}; </span>
Use higher level abstractions like map to reduce the amount of code
<span>// impure, splice mutates the array </span><span>var firstThree = function(arr) { </span> <span>return arr.splice(0,3); // arr may never be the same again </span><span>}; </span> <span>// pure, slice returns a new array </span><span>var firstThree = function(arr) { </span> <span>return arr.slice(0,3); </span><span>}; </span>
Reduce functions to their simplest forms
<span>let items = ['a','b','c']; </span><span>let newItems = pure(items); </span><span>// I expect items to be ['a','b','c'] </span>
Delete code until it stops working
We don’t need a function at all for such a simple task, the language provides us with sufficient abstractions to write it out verbatim.
<span>import _ from 'ramda'; </span><span>import $ from 'jquery'; </span> <span>var Impure = { </span> <span>getJSON: _.curry(function(callback<span>, url</span>) { </span> $<span>.getJSON(url, callback); </span> <span>}), </span> <span>setHtml: _.curry(function(sel<span>, html</span>) { </span> <span>$(sel).html(html); </span> <span>}) </span><span>}; </span> <span>var img = function (url) { </span> <span>return $('<img />', { src: url }); </span><span>}; </span> <span>var url = function (t) { </span> <span>return 'http://api.flickr.com/services/feeds/photos_public.gne?tags=' + </span> t <span>+ '&format=json&jsoncallback=?'; </span><span>}; </span> <span>var mediaUrl = _.compose(_.prop('m'), _.prop('media')); </span><span>var mediaToImg = _.compose(img, mediaUrl); </span><span>var images = _.compose(_.map(mediaToImg), _.prop('items')); </span><span>var renderImages = _.compose(Impure.setHtml("body"), images); </span><span>var app = _.compose(Impure.getJSON(renderImages), url); </span><span>app("cats"); </span>
Being able to simply test our programs is a key benefit of pure functions, so in this section we’ll set up a test harness for our Flickr module we were looking at earlier.
Fire up a terminal and have your text editor poised and ready, we’ll use Mocha as our test runner and Babel for compiling our ES6 code.
<span>var app = (tags)=> { </span> <span>let url = <span>`http://api.flickr.com/services/feeds/photos_public.gne?tags=<span>${tags}</span>&format=json&jsoncallback=?`</span> </span> $<span>.getJSON(url, (data)=> { </span> <span>let urls = data.items.map((item)=> item.media.m) </span> <span>let images = urls.map((url)=> $('<img />', { src: url }) ) </span> <span>$(document.body).html(images) </span> <span>}) </span><span>} </span><span>app("cats") </span>
Mocha has a bunch of handy functions like describe and it for breaking up our tests and hooks such as before and after for setup and teardown tasks. assert is a core node package that can perform simple equality tests, assert and assert.deepEqual are the most useful functions to be aware of.
Let’s write our first test in test/example.js
<span>let flickr = (tags)=> { </span> <span>let url = <span>`http://api.flickr.com/services/feeds/photos_public.gne?tags=<span>${tags}</span>&format=json&jsoncallback=?`</span> </span> <span>return fetch(url) </span> <span>.then((resp)=> resp.json()) </span> <span>.then((data)=> { </span> <span>let urls = data.items.map((item)=> item.media.m ) </span> <span>let images = urls.map((url)=> $('<img />', { src: url }) ) </span> <span>return images </span> <span>}) </span><span>} </span><span>flickr("cats").then((images)=> { </span> <span>$(document.body).html(images) </span><span>}) </span>
Open up package.json and amend the "test" script to the following
<span>let responseToImages = (resp)=> { </span> <span>let urls = resp.items.map((item)=> item.media.m ) </span> <span>let images = urls.map((url)=> $('<img />', { src: url })) </span> <span>return images </span><span>} </span>
Then you should be able run npm test from the command line to confirm everything is working as expected.
<span>let urls = (data)=> { </span> <span>return data.items.map((item)=> item.media.m) </span><span>} </span><span>let images = (urls)=> { </span> <span>return urls.map((url)=> $('<img />', { src: url })) </span><span>} </span><span>let responseToImages = _.compose(images, urls) </span>
Boom.
Note: You can also add a -w flag at the end of this command if you want mocha to watch for changes and run the tests automatically, they will run considerably faster on re-runs.
<span>let responseToImages = (data)=> { </span> <span>return images(urls(data)) </span><span>} </span>
Let’s add our module into lib/flickr.js
<span>let items = ['a', 'b', 'c']; </span><span>let upperCaseItems = ()=> { </span> <span>let arr = []; </span> <span>for (let i = 0, ii = items.length; i < ii; i++) { </span> <span>let item = items[i]; </span> arr<span>.push(item.toUpperCase()); </span> <span>} </span> items <span>= arr; </span><span>} </span>
Our module is exposing two methods: flickr to be publicly consumed and a private function _responseToImages so that we can test that in isolation.
We have a couple of new dependencies: jquery, underscore and polyfills for fetch and Promise. To test those we can use jsdom to polyfill the DOM objects window and document and we can use the sinon package for stubbing the fetch api.
<span>// pure </span><span>function add(a<span>, b</span>) { </span> <span>return a + b; </span><span>} </span>
Open up test/_setup.js and we’ll configure jsdom with our globals that our module depends on.
<span>// impure </span><span>var minimum = 21; </span><span>var checkAge = function(age) { </span> <span>return age >= minimum; // if minimum is changed we're cactus </span><span>}; </span>
Our tests can sit in test/flickr.js where we’ll make assertions about our functions output given predefined inputs. We “stub” or override the global fetch method to intercept and fake the HTTP request so that we can run our tests without hitting the Flickr API directly.
<span>// pure </span><span>var checkAge = function(age) { </span> <span>var minimum = 21; </span> <span>return age >= minimum; </span><span>}; </span>
Run our tests again with npm test and you should see three assuring green ticks.
<span>// impure, splice mutates the array </span><span>var firstThree = function(arr) { </span> <span>return arr.splice(0,3); // arr may never be the same again </span><span>}; </span> <span>// pure, slice returns a new array </span><span>var firstThree = function(arr) { </span> <span>return arr.slice(0,3); </span><span>}; </span>
Phew! We’ve successfully tested our little module and the functions that comprise it, learning about pure functions and how to use functional composition along the way. We’ve separated the pure from the impure, it’s readable, comprised of small functions, and it’s well tested. The code is easier to read, understand, and modify than the unreasonably pure example above and that’s my only aim when refactoring code.
Pure functions, use them.
—
That’s all for now! Thanks for reading and I hope you have found this a good introduction to functional programming, refactoring and testing in JavaScript. It’s an interesting paradigm that’s making waves at the moment, due largely to the growing popularity of libraries like React, Redux, Elm, Cycle and ReactiveX which encourage or enforce these patterns.
Jump in, the water is warm.
Pure functions are a fundamental concept in functional programming. They are functions that always produce the same output for the same input and have no side effects. This means that they don’t alter any state outside their scope or depend on any external state. This makes them predictable and easy to test, as you only need to consider the input and output, without worrying about external factors. Pure functions also promote code reusability and readability, making your code easier to understand and maintain.
Functional programming is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. This is in contrast to imperative programming, where programs are composed of statements which change global state when executed. Functional programming promotes higher-level abstractions, like functions as first-class citizens, and encourages programming with expressions instead of statements. This leads to a more declarative and expressive style of programming that is easier to reason about.
Implementing pure functions in JavaScript involves ensuring that your function always produces the same output for the same input and does not produce any side effects. Here’s an example:
function add(a, b) {
return a b;
}
In this example, the add function is a pure function because it always returns the same result given the same arguments and does not modify any external state.
Pure functions offer several benefits in JavaScript. They make your code more predictable and easier to test and debug, as you only need to consider the input and output of the function. They also make your code more readable and maintainable, as they promote a clear and simple programming style. Furthermore, pure functions are highly reusable and composable, allowing you to build more complex functionality with less code.
While pure functions offer many benefits, they also present some challenges. One of the main challenges is that JavaScript is not a purely functional language, and it allows side effects and mutable data. This means that you need to be careful to avoid unintentionally introducing side effects in your functions. Additionally, using pure functions can sometimes lead to more verbose code, as you need to avoid mutating data and instead return new data.
Functional programming is particularly well-suited to concurrency and parallelism. Because pure functions do not have side effects, they can be safely executed in parallel without worrying about race conditions or data corruption. This makes functional programming a powerful tool for developing concurrent and parallel applications, particularly in a multi-core and distributed computing environment.
Function composition is a fundamental concept in functional programming. It involves combining two or more functions to create a new function. The result of one function is used as the input to the next function. This allows you to build complex functionality from simple functions, promoting code reusability and readability.
Immutability is a key principle in functional programming. It means that once a data structure is created, it cannot be changed. Instead, if you want to modify a data structure, you create a new one with the desired changes. This avoids side effects and makes your code safer and easier to reason about.
In functional programming, state is handled carefully to avoid side effects. Instead of changing state, functional programming often uses pure functions that return new state. This makes the state predictable and easy to manage. Some functional programming languages also offer advanced features for state management, such as monads in Haskell.
Functional programming can be used in a wide range of applications. It’s particularly useful in situations where concurrency and parallelism are important, such as in multi-core and distributed computing. Functional programming is also commonly used in data processing and analytics, where pure functions and immutability can help to ensure data integrity. Furthermore, functional programming concepts are increasingly being adopted in front-end development, with popular frameworks like React.js using a functional style for component development.
The above is the detailed content of An Introduction to Reasonably Pure Functional Programming. For more information, please follow other related articles on the PHP Chinese website!