Ramda-style thinking: declarative programming

Original author: Randy Coulman
  • Transfer
  • Tutorial
1. First steps
2. Combining functions
3. Partial use (currying)
4. Declarative programming
5. Shadowless notation
6. Immutable and objects
7. Immutable and arrays
8. Lenses
9. Conclusion

This post is the fourth part of the series on functional programming called "Ramda style thinking."

In the third part, we talked about combining functions that can take more than one argument, using partial application and currying techniques.

When we begin to write small functional building blocks and combine them, we find that we need to write many functions that will wrap JavaScript operators such as arithmetic, comparison, logic, and flow control. It may seem tedious, but we are behind Ramda.

But first, a small introduction.

Imperative vs Declarative

There are many different ways to separate programming languages ​​and writing styles. These are static typing versus dynamic typing, interpreted languages ​​and compiled languages, high-level and low-level, and so on.

Another similar division is imperative programming versus declarative.

Without diving deeper into it, imperative programming is a programming style in which programmers tell the computer what to do, explaining how to do it. Imperative programming gives many constructs that we use every day: Flow Control ( if- then- elsesyntax and cycles), arithmetic operators ( +, -, *, /), comparison operators ( ===,>, <Etc.), and logical operators ( &&, ||, !).

Declarative programming is a programming style in which programmers tell the computer what to do, explaining to them what they want. The computer further must determine how to obtain the desired result.

One of the classic declarative languages ​​is Prolog. In Prolog, a program consists of a set of facts and a set of inference rules. You start the program by asking a question, and Prolog's set of inference rules uses facts and rules to answer your question.

Functional programming is seen as a subset of declarative programming. In a functional program, we declare functions and then explain to the computer what we want to do by combining these functions.

Even in declarative programs, it is necessary to perform similar tasks that we perform in imperative programs. Flow control, arithmetic, comparisons, and logic are still the basic building blocks we need to work with. But we need to find ways to express these constructions in a declarative style.

Declarative substitutes

Since we program in JavaScript, an imperative language, it’s normal to use standard imperative constructs when writing “normal” JavaScript code.

But when we write functional transformations using pipelines and similar constructions, imperative constructions cease to fit with the created code structure.

Let's look at some of the basic building blocks that Ramda provides in order to help us get out of this unpleasant situation.


In the second part, we implemented a series of arithmetic transformations to demonstrate the assembly line:

const multiply = (a, b) => a * b
const addOne = x => x + 1
const square = x => x * x
const operate = pipe(
operate(3, 4) // => ((3 * 4) + 1)^2 => (12 + 1)^2 => 13^2 => 169

Notice how we write the functions for all the basic building blocks we want to use.

Ramda provides add , subtract , multiply, and divide functions for use in place of standard arithmetic operations. So we can use the ramd multiplyfunction where we used the self-written function, we can take the advantage of the curried function addto replace ours addOne, and we can also write squarewith multiply.

const square = x => multiply(x, x)
const operate = pipe(

add(1)very similar to the increment operator ( ++), but the increment operator modifies a variable so that it causes a mutation. As we learned from the first part , immutability is the main principle of functional programming, so we don’t want to use ++its cousin either --.

We may use add(1)and subtract(1)to increase or decrease, but since these two operations are common, Ramda provides inc and dec instead.

So we can simplify our pipeline a little more:

const square = x => multiply(x, x)
const operate = pipe(

subtractis a replacement for the binary operator -, but we still have a unary operator -to negate the value. We can also use multiply(-1), but Ramda provides a negate function to accomplish this task.


Also in the second part, we wrote several functions to determine whether a person is eligible to vote. The final version of that code was as follows:

const wasBornInCountry = person => person.birthCountry === OUR_COUNTRY
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => person.age >= 18
const isCitizen = either(wasBornInCountry, wasNaturalized)
const isEligibleToVote = both(isOver18, isCitizen)

Please note that some of our functions use standard comparison operators ( ===and >=in this case). As you can now, Ramda also provides substitutes for all of this.

Let's convert our code to using equals instead ===and gte instead >=.

const wasBornInCountry = person => equals(person.birthCountry, OUR_COUNTRY)
const wasNaturalized = person => Boolean(person.naturalizationDate)
const isOver18 = person => gte(person.age, 18)
const isCitizen = either(wasBornInCountry, wasNaturalized)
const isEligibleToVote = both(isOver18, isCitizen)

Ramda also provides gt for >, lt for, <and lte for. <=

Note that these functions seem to take their arguments in the normal order (is the first argument larger than the second?). This makes sense when we use them in isolation, but can be confusing when combining functions. These functions violate the “data comes last” principle, so we need to be careful when we use them in our assembly lines and similar situations. And that is where flip and placeholder ( __ ) can benefit.

In addition to equalsthere is still identical to determine whether two values ​​are references to the same space in memory.

There are a number of cases of main applications for ===: checking that a string or array is empty ( str === ''or arr.length === 0) and checking whether a variable is equal to nullor undefined. Ramda provides convenient functions for both cases: isEmpty and isNil .


In the second part (and a little higher), we used the functions bothand eitherinstead of the &&and operators ||. We also talked about complementfor places with !.

These combined functions work great when functions combine an operation on the same value. Written above wasBornInCountry, wasNaturalizedand isOver18all applied to the object of the person.

But sometimes we need to apply &&, ||and !to different meanings. For special cases, Ramda provides us with the functions and , or and not . I think as follows: and, orand notare working with values, while both, eitherandcomplementwork with functions.

It is mainly ||used to get default values. For example, we can write something like this:

const lineWidth = settings.lineWidth || 80

This is a common idiom, and most often working, but relying on JavaScript logic for determining "falsity." What if 0is a valid parameter? Since it 0is a false value, we get the line value equal to 80.

We can use the function isNilthat we just learned about above, but Ramda again has a more logical option for us: defaultTo .

const lineWidth = defaultTo(80, settings.lineWidth)

defaultTochecks the second argument to isNil. If the check fails, it will return the received value, otherwise it will return the first argument passed to it.


Controlling the flow of execution is less important in functional programming, but sometimes it is necessary. The collection of iterative functions that we talked about in the first part takes care of most situations with loops, but the conditions are still pretty important.


Let's write a function, forever21which gets the year and returns the next one. But, as her name indicates to us, starting at age 21, he will remain in that meaning.

const forever21 = age => age >= 21 ? 21 : age + 1

Note that our condition ( age >= 21) and the second branch ( age + 1) can both be written as functions age. We can rewrite the first branch ( 21) as a constant function ( () => 21). Now we will have three functions that accept (or ignore) age.

Now we are in a position where we can use the ifElse function from Ramda, which is the equivalent of a structure if...then..elseor its shorter cousin, the ternary operator ( ?:).

const forever21 = age => ifElse(gte(__, 21), () => 21, inc)(age)

As we mentioned above, comparison functions do not work like union functions, so here we need to start using placeholder ( __). We can also apply lteinstead:

const forever21 = age => ifElse(lte(21), () => 21, inc)(age)

In this case, we should read it as "21 is less or equal age." I am going to stick with the alternate version for the remainder of the post, as I find this more readable and less confusing.


Constant functions are very useful in situations like this. As you can imagine, Ramda provides us with a shortcut. In this case, the abbreviation is called always .

const forever21 = age => ifElse(gte(__, 21), always(21), inc)(age)

Ramda also provides T and F as further abbreviations for always(true)andalways(false)


Let's try to write another function alwaysDrivingAge. This function accepts ageand returns it if its value is gte16. If it is less than 16, then it will return 16. This allows anyone to pretend that he is old enough to drive a car, even if it is not:

const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), a => a)(age)

The second branch of comparison ( a => a) is another typical pattern in functional programming. This is known as "identity" ( I don’t know the exact translation of the term "identity function", just choose this one - approx. Trans. ). That is, it is a function that simply returns the argument that it received.

As you can expect, Ramda provides us with an identity function :

const alwaysDrivingAge = age => ifElse(lt(__, 16), always(16), identity)(age)

identitycan take more than one argument, but always return only the first. If we want to return something other than the first argument, there is a more general nthArg function for this . This is a much less common situation than use identity.

"When" and "unless"

An expression ifElsein which one of the logical branches is identity is also a typical pattern, so Ramda provides us with more abbreviation methods.

If, as in our case, the second branch is an identity, we can use when instead of ifElse:

const alwaysDrivingAge = age => when(lt(__, 16), always(16))(age)

If the first branch of the condition is an identity, we can use unless . If we turn our condition of use over gte(__, 16), we can use unless.

const alwaysDrivingAge = age => unless(gte(__, 16), always(16))(age)


Ramda also provides a cond function that can replace an expression switchor a chain of expressions if...then...else.

const water = temperature => cond([
  [equals(0),   always('water freezes at 0°C')],
  [equals(100), always('water boils at 100°C')],
  [T,           temp => `nothing special happens at ${temp}°C`]

I did not need to use condRamda in my code, but I wrote similar code in Lisp many years ago, so it condfeels like an old friend.


We looked at the set of functions that Ramda provides us with for transforming our imperative code into declarative functional code.


You may have noticed that the last few functions that we wrote ( forever21, drivingAgeand water) all take parameters, create a new function and then apply this function to the parameter.

This is a common pattern, and again Ramda provides us with tools to bring it all to a cleaner look. The next post, " Pointless Notation, " looks at ways to simplify functions that follow a similar pattern.

Also popular now: