This article was reviewed by Yaphi Berhanu, Vildan Softic, Jani Hartikainen and Dan Prince. Thanks to all SitePoint peer reviewers for getting SitePoint content to its best!
One of the charms of JavaScript is its functional programming characteristics. From the very beginning, functions were first-class citizens in the JavaScript world. This makes it possible to write elegant and expressive code that can be easily put together in many ways. However, having the ability to perform functional programming alone cannot automatically implement functional programming. Ramda.js is a very popular library (with over 4k stars on GitHub) that we can use to help us get started with JavaScript for functional programming.
Key points
R.map
, R.prop
and R.curry
, simplifying the operation of data structures and enhancing the readability and maintainability of the code. Beginner
To make the most of Ramda.js, we should be familiar with its advantages by creating a small Node.js project. We can simply install it through the Node package manager (npm).
npm install ramda
Usually, we will simply import the functionality of the library into the namespace R. This way, all calls to the Ramda method will have the R. prefix.
var R = require('ramda');
Of course, nothing can stop us from using Ramda.js in front-end code. In the browser, we just need to include the correct path to the library copy. This may be as simple as the following HTML code snippet.
<🎜>
Ramda.js does not use any DOM or Node.js-specific features. It is just a language library/extension and builds on the structures and algorithms already exposed by the JavaScript runtime (as standardized in ECMAScript 5). Ready to conduct in-depth research? Let's take a look at the practical application of some functions!
Concept
The most important concept in functional programming is the concept of pure functions. Pure functions are idempotent and do not change any state. From a mathematical point of view, this makes sense, because functions like sin(x) seem very natural and do not depend on any external state. In addition to pure functions, we also want to have single-parameter functions. They are the most primitive. A zero-parameter function usually means that the external state will be changed, so it is not a pure function. But in languages like JavaScript, we usually have functions with multiple parameters.
The ability to have higher-order functions (i.e., functions that can take functions as input and emit functions as output) combined with closures (capturing local variables) provides us with a good way: Currying. Currying is a process in which a function with multiple (assuming n) parameters is converted into a function with a single parameter, which returns another function with a single parameter. This will continue until all required parameters are collected. Suppose we want to use the Ramda.js helper function to write a single parameter wrapper that tests whether its parameters are strings. The following code will do the job.
npm install ramda
This can be done more easily with currying. Since R.is is part of Ramda.js, if we provide fewer parameters than the ones required by the function, the library will automatically return a curry function:
var R = require('ramda');
This is more expressive. Since we called R.is with a single argument, we got a function. In the second call (remember, the original function call takes two parameters), we get the result. But what if we didn't use Ramda.js helper functions at the beginning? Let's assume we have defined the following function somewhere in the code:
<🎜>
This is a complete second-order polynomial. It has four parameters that allow all possible values. But usually, we just want to change x for a fixed set of parameters a, b, and c. Let's see how to convert it using Ramda.js:
function isString (test) { return R.is(String, test); } var result = isString('foo'); //=> true
Similarly, we are able to simply use parameter evaluation to create alias for a specific subset. For example, the equation x - 1 can be obtained by:
var isString = R.is(String); var result = isString('foo'); //=> true
In the case that the number of parameters is not given by the parameters of our function, we need to use curryN and explicitly specify the number of parameters. Currying is at the heart of Ramda.js, but the library doesn't seem so interesting without more. Another important concept in functional programming is invariance.
The easiest way to prevent a function from changing its state is to just use a data structure that cannot be changed. For simple objects, we need a read-only accessor, for example:
is not allowed. In addition to declaring read-only properties, we can also convert them to getter functions:var quadratic = (a, b, c, x) => x * x * a + x * b + c; quadratic(1, 0, 0, 2); //=> 4 quadratic(1, 0, 0)(2); //=> TypeError: quadratic(..) is not a function
This is much better now, however, the object can still be changed. This means someone can add a custom definition of the getX function, for example:
var quadratic = R.curry((a, b, c, x) => x * x * a + x * b + c); quadratic(1, 0, 0, 2); //=> 4 quadratic(1, 0, 0)(2); //=> 4
npm install ramda
The best way to achieve invariance is to use Object.freeze. Combined with the const keyword, we can introduce an invariant variable that cannot be changed.
var R = require('ramda');
Another example involves lists. Adding an element to an immutable list requires copying the original list and adding a new element to the end. Of course, we can also use the invariance knowledge of the original object to optimize the implementation. This way we can replace the copy with a simple reference. Essentially, this might turn into a linked list. We should realize that standard JavaScript arrays are mutable and therefore need to be copied to ensure correctness. Methods such as append() work on JavaScript arrays and return such arrays. The operation is idempotent; if we call the function with the same arguments multiple times, we will always get the same result.
<🎜>
There is also a remove method that returns the given array but does not contain the specified entry. It works as follows:
function isString (test) { return R.is(String, test); } var result = isString('foo'); //=> true
Since it has a flexible number of parameters, we need the curryN function mentioned earlier to apply curryization. There are also some useful general helper functions available.
Practical Methods
The most important concept of all helper functions is that the parameters are ordered to facilitate currying. The more frequently the parameter is changed, the less likely it is to be before the other parameters.
Of course, you can find common functions in Ramda.js, such as sum and range:
var isString = R.is(String); var result = isString('foo'); //=> true
For range() helper functions, we can create a wrapper using curry:
var quadratic = (a, b, c, x) => x * x * a + x * b + c; quadratic(1, 0, 0, 2); //=> 4 quadratic(1, 0, 0)(2); //=> TypeError: quadratic(..) is not a function
What if we want to wrap it with a fixed (exclusive) maximum? Ramda.js meets our needs by using special parameters represented by R.__:
var quadratic = R.curry((a, b, c, x) => x * x * a + x * b + c); quadratic(1, 0, 0, 2); //=> 4 quadratic(1, 0, 0)(2); //=> 4
In addition, Ramda.js tries to use a "better" solution to provide alternatives to JavaScript core functions such as Array.prototype.map. The "better" solution has different parameter order and currying out of the box. For map function, as shown below:
var xOffset = quadratic(0, 1, -1); xOffset(0); //=> -1 xOffset(1); //=> 0
Another useful utility is the prop function, which tries to get the value of the specified property. If the given property does not exist, undefined is returned. This may be ambiguous if the value is indeed undefined, but in practice we rarely care about it.
var position = { x: 5, y: 9 }; position.x = 10; // works!
If the previously introduced method doesn't convince you that Ramda.js may provide something useful, then the next method may be more interesting. This time we will not discuss a specific example, but instead look at arbitrary selected scenarios. Suppose we have two lists and we want to connect them. Using the zip function is actually very simple. However, the usual results (arrays of elements, which themselves are double-valued arrays) may not be the result we want. This is where the zipWith function comes into play. It maps values to a single value using arbitrary functions.
var position = (function (x, y) { return { getX: () => { return x; }, getY: () => { return y; } }; })(5, 9); position.getX() = 10; // does not work!
Similarly, we can introduce dot product to the vector:
npm install ramda
We compress two arrays by multiplication (generating [1, 4, 9]) and pass the result to the sum function. In any case, using enumerable objects is an important topic. It is no surprise that Ramda.js provides many useful helper functions. We have introduced R.map to apply functions to each element. Similarly, there are some helper functions that reduce the number of elements. A single value can be generated by the most general filter function (generates another array) or by the reduce function.
There are some useful helper functions for the operation of the array. For example, using chain, we can easily merge arrays. Suppose we have a function primeFactorization that uses numbers as input and gives an array with prime factors as output, we can combine the result of applying the function with a set of numbers as follows:
var R = require('ramda');
So far, everything has been going well. The biggest question now is: What are the benefits of our daily work by using these concepts introduced by Ramda.js? Let's assume we have the following (already very good looking) code snippet.
<🎜>
How to use Ramda.js to make it more readable? Well, the first line is good enough. The second line is already very confusing. What we really want is to extract the posts property of the provided parameters. Finally, we have a slightly confusing third line. Here we try to iterate over all posts (provided by parameters). Again, its sole purpose is to extract specific properties. How about the following solution?
function isString (test) { return R.is(String, test); } var result = isString('foo'); //=> true
This is probably the best solution for readability thanks to Ramda.js-enabled functional programming. However, we should note that the "fat arrow" syntax introduced in ECMAScript 6 also leads to very concise and easy-to-read code:
var isString = R.is(String); var result = isString('foo'); //=> true
This is almost as easy to read without any Ramda.js knowledge. Furthermore, we reduce the number of abstractions – this will only benefit performance and maintainability.
Lens
Finally, we should also discuss useful object helper functions. It is worth mentioning here that the lens function is. A lens is a special object that can be passed along with an object or array to certain Ramda.js functions. It allows these functions to retrieve or convert data from specific properties or indexes of an object or array, respectively. Suppose we have an object with two keys x and y - just like the invariance example given at the beginning of the article. Instead of wrapping the object in another object with getter and setter methods, we can create a lens to "focus" on the properties of interest. To create a lens that accesses the x attribute of an object, we can do the following:
var quadratic = (a, b, c, x) => x * x * a + x * b + c; quadratic(1, 0, 0, 2); //=> 4 quadratic(1, 0, 0)(2); //=> TypeError: quadratic(..) is not a function
Although prop is a standard getter (this has been introduced), assoc is a setter function (three value syntax: key, value, object). Now we can use the functions in Ramda.js to access the properties defined by this lens.
npm install ramda
Note that this operation does not touch the given position object (whether we freeze it or not). It should be noted that set is just a special case of over , which is similar but takes functions instead of arbitrary values. The function will then be used to convert the value. For example, the following call multiplies the x coordinate by 3:
var R = require('ramda');
Ramda.js, lodash or other?
A reasonable question is of course why Ramda.js is chosen - why don't we use lodash or something? Of course, one might argue that Ramda.js is updated, so it must be better, but that's far from the truth. The truth is that Ramda.js is built with functional principles in mind—with a new approach to parameter placement and selection (for JavaScript libraries). For example, the list iterator in Ramda.js passes only items by default, not lists. On the other hand, the standard for other libraries (such as lodash) is to pass items and indexes to callback functions. This seems like a slight problem, but it prevents you from using convenient built-in functions like parseInt() (which takes an optional second argument), while using Ramda.js, this works fine. Ultimately, what you choose depends on the specific requirements or the experience and/or knowledge of the team, but there are certainly some good reasons to get Ramda.js the attention it deserves.
Further reading
Conclusion
Functional programming should not be considered a panacea. Instead, it should be seen as a natural addition to our existing toolbox, which provides us with greater composability, greater flexibility and greater fault tolerance/rotability. Modern JavaScript libraries have tried to take advantage of some functional concepts. Ramda.js is a powerful tool that extends your own functional utility library. What do you think of functional programming? In what aspects do you think it performs well? Please let me know in the comments!
FAQs (FAQs) on Functional Programming with Ramda
Ramda is a practical functional library for JavaScript programmers. It is designed for functional programming style, which makes it easier to create functional pipelines and never changes user data. The main advantage of using Ramda is that it emphasizes invariance and side-effect-free functions. This means that the function does not change its input data, making your code easier to predict and test. Ramda's functions are automatically curried, which allows you to easily build new functions from old functions without getting lost in a bunch of brackets or callback hell.
Unlike other JavaScript libraries, Ramda is designed to support functional programming and does not change data. It provides a set of practical functions that are curry by default, meaning they are intended to be used together to create new functions. This is very different from libraries like Underscore or Lodash, which are not curry-like by default and usually require you to write extra code to get the same results. Ramda's API is also more consistent and easier to use, with the emphasis on simplicity and readability.
Yes, Ramda can be used with other JavaScript libraries and frameworks. It is a standalone library that does not depend on any other libraries or frameworks, and does not modify JavaScript object prototypes. This makes it safe to work with other libraries or frameworks without the need to worry about conflicts or unexpected side effects. Whether you are using jQuery, React, Angular, Vue, or any other library or framework, you can use Ramda to help write cleaner, more functional code.
Ramda is a great tool for newbies in functional programming. Its API design is simple and intuitive, and its naming conventions are clear and consistent. The documentation is also very detailed and there are many examples to get started. But, like any new tool or paradigm, there is a learning curve. It may take some time to get used to functional thinking and understand how to use Ramda's functions effectively. However, with practice, you will find that it can greatly simplify your code and make it easier to understand.
Ramda treats null and undefined values as null values, similar to how other functional programming languages handle them. This means you can safely pass null or undefined to Ramda's functions without causing errors or exceptions. However, it is a good idea to always check null or undefined values before passing them to a function to avoid unexpected behavior.
Yes, Ramda can be used in Node.js environments. It is a common library that can be used in browsers and Node.js. You can install it via npm and need it in your Node.js module like any other package.
Ramda does not have built-in asynchronous operation support, because it is mainly a synchronous library. However, you can use it with other libraries that support asynchronous operations, such as Promises or async/await. You can also use Ramda's functions in asynchronous functions or in then callbacks.
Ramda is an open source project and is welcome to contribute at any time. You can contribute by reporting bugs, suggesting new features, improving documentation, or submitting pull requests. Before you contribute, it is best to read the Contribution Guide on the Ramda GitHub page.
Yes, Ramda is actively maintaining and regularly updating. Maintenance personnel are committed to keeping the library up to date and to resolve any issues or errors that arise. You can check the GitHub page for the latest updates and versions.
Yes, Ramda is licensed under a MIT license, which means you can use it for commercial projects. However, it is a good idea to always read the full license agreement to understand your rights and responsibilities.
The above is the detailed content of Hands-on Functional Programming with Ramda.js. For more information, please follow other related articles on the PHP Chinese website!