DEV Community

Cover image for Farewell, Ramda
Pavel Marianov
Pavel Marianov

Posted on • Originally published at hackernoon.com

Farewell, Ramda

Intro

Have you ever come across a piece of code that's two years old and struggled to make changes to it? You spend a lot of time trying to figure out where to make adjustments, but it seems impossible. Sometimes, it's so confusing that it feels like you're trying to decipher a cryptic message from an ancient civilization. I have, and in the majority of cases, Ramda was involved. This experience was not singular, leading me to explore alternatives and reconsider my code maintenance strategies.

Brave new Ramda

Ramda is a fantastic JavaScript library that embraces functional programming principles and equips you with a robust set of utility functions. It's all about empowering you to work with immutable data, compose functions, and bring a declarative flair to your code. With Ramda, you can enjoy automatic currying, seamless function composition, and effortless manipulation of immutable data. Plus, it encourages you to embrace the elegance of point-free style, where functions are defined without explicitly mentioning their arguments. Ramda truly shines in making functional programming more approachable, expressive, and downright enjoyable in your JavaScript projects.

When I started using Ramda, I experienced the "WOW" effect. I enjoyed how elegantly declarative my code became, and all the helpful functions in its toolkit allowed me to express complex logic in a single statement. Every time I managed to solve a task with it, I felt a sense of reward in my brain. The more logic I was able to incorporate, the greater the satisfaction. Apart from that sentiment, Ramda truly improved the code in many ways, such as maintaining data immutability, empowering declarative expressions, and solving numerous routine tasks with its handy set of helpers, among other benefits.

Decay of Ramda and The Renaissance of JavaScript

However, as JavaScript evolved, I, along with many of my colleagues, found ourselves increasingly relying on native JavaScript solutions, gradually replacing libraries such as Ramda, Lodash, and others in favour of the built-in tools and features of the language.

Ever encountered that sweet feeling of writing straightforward, no-fuss code using just the built-in features of a language? I bet we all have. As developers, we frequently gravitate towards what we know best and, in this case, that's good old native JavaScript.

Just like enjoying a perfectly brewed cup of coffee without any frills, there's a certain delight in working with native JavaScript. It offers us a warm blanket of familiarity and straightforwardness that lets us hit the ground running without having to learn the ins and outs of a new library.

From its early days with ES5, JavaScript has grown leaps and bounds, evolving through ES2015, ES2017, and up to the shiny and new ES2020. Each update brought with it cool new features that made our coding lives much simpler and greatly improved the functional and declarative nature of JavaScript code.

Let's start with higher-order functions like map(), reduce(), and filter() dealing with arrays turned into a breeze. It felt as easy as pie.

Next, we got the awesome arrow functions. These new additions were sleek and straightforward, making our code simpler to read and comprehend.

Then came template literals - they transformed how we handled strings. No more messy concatenation, only smooth, reliable string creation.

The spread syntax was another welcome addition, making it a cinch to expand arrays or objects, to maintain immutability. And last but not least, we got the handy trio of destructuring assignment, optional chaining, and the nullish coalescing operator. They worked wonders for our code, making it cleaner and easier on the eyes.

There's a sense of comfort and simplicity that comes with using native JavaScript features, a familiarity that allows developers to create without extra layers of learning. Many of us are drawn to the core of the language, the very fabric it's woven from. And why not? These native features are not just the heart of JavaScript, they're also designed with performance in mind, as lightweight and agile as a gazelle.

Compared to libraries like Ramda, which wrap your code in an extra layer of abstraction, native JavaScript runs as swift as a coursing river.

These features benefit from the optimisations in JavaScript engines like V8. The creators of V8, the engine behind Google Chrome and Node.js, continuously optimise for these native features. This ensures even smoother and faster execution, making your JavaScript code run like a well-oiled machine.

But it's not just about speed. It's about integration and compatibility too. These native features dance smoothly with other parts of the language, working in harmony with different APIs and the broad ecosystem of JavaScript.

Another great thing about native JavaScript is its compatibility with different versions of ECMAScript and the fact that it has minimal dependencies. It's a language that keeps improving, with continuous updates to ECMAScript. This constant growth and adaptation make native JavaScript a top choice for many developers. Why look for something new when the original does the job so well?

Beating your head against the ramdamised TypeScript

When you're moving your project to TypeScript and trying to incorporate your existing Ramda code, a whole new set of issues can pop up. TypeScript brings static typing to the table, which offers great benefits like improved code safety and increased developer productivity. However, when you mix in Ramda with TypeScript, things can get a bit tricky due to differences in typing approaches and compatibility.

One major headache is making sure that the types in your Ramda functions align with the TypeScript typings. Ramda does provide some TypeScript typings, but they might not cover all scenarios or be up to date with the latest Ramda version. This can lead to mismatches between expected and inferred types, forcing you to roll up your sleeves and handle type annotations manually or create custom typings.

Another challenge lies in the functional programming style of Ramda, with its fancy currying and function composition. TypeScript's type inference might struggle to keep up with all the twists and turns, so you might find yourself adding explicit type annotations or extra assertions to keep everything in check. It can get a bit messy and might make your code less readable and maintainable.

To top it off, if your Ramda code was written without TypeScript in mind, it might lack proper type annotations and documentation. That means you'll have to put in extra time and effort to refactor and adapt the codebase to fit TypeScript conventions and ensure type safety.

On the other hand, using native JavaScript features with TypeScript solves many of these problems. TypeScript can handle the types accurately without any fuss, making your life a lot easier. The smooth integration and improved TypeScript support make native JavaScript features an attractive option for TypeScript projects, especially when you're aiming for type safety and efficient development.

Stop talking, show me the code

Let’s have a look at this code written with Ramda. It doesn’t perform a very complex logic, the case is quite straightforward itself. However, even for this, my brain has to make an effort in order to grasp the logic behind this snippet.

const result = R.pipe(
  R.filter(R.both(
    R.propSatisfies(R.gt(R.__, 25), 'age'),
    R.pathEq(['address', 'country'], 'USA')
  )),
  R.map(R.prop('name'))
)(users)

// πŸ€” πŸ€” πŸ€”
Enter fullscreen mode Exit fullscreen mode

The code filters a list of users based on their age and their country of residence, specifically looking for users older than 25 residing in the USA. The Ramda example above applies functional programming principles using R.pipe() to string together multiple operations. While the logic isn't the most convoluted I've ever seen, the layered composition of functions does require a moment (or two) of pause to understand.

We can rewrite this with Lodash and make it a bit better

const resultL2 = _.map(
  _.filter(
    users,
    user => user.age > 25 && user.address.country === 'USA'
  ),
  'name'
)

// πŸ‘ŒπŸ‘ŒπŸ‘Œ
Enter fullscreen mode Exit fullscreen mode

Immediately, the Lodash version feels more approachable. It simplifies our life by directly expressing the filter conditions within the callback function, using filter() and map(). Yet, while it's an improvement, there's still room for simplification.

Let’s go further and rewrite this using only native JS:

const result = users
  .filter(
    user => user.age > 25 && user.address.country === 'USA'
  )
  .map(user => user.name)

// πŸ₯ΉπŸ₯ΉπŸ₯Ή
Enter fullscreen mode Exit fullscreen mode

Beautiful!

The syntax is straightforward, intuitive, and without any abstractions to distract us from the core logic. We see a list of operations that are easy to read and understand. We use the filter() and map() array methods to achieve the same filtering and mapping operations. The filtering conditions are directly written within the callback functions, resulting in a more readable code snippet. Indeed, even my grandma can read this.

For those used to traditional coding styles, Ramda's heavy use of functional programming and currying, coupled with unusual naming conventions, can make its code tricky to understand. Especially when you need to switch between different code styles within a project, this can be even more challenging. Essentially, if you're not used to these specific practices, reading Ramda code can be like trying to solve a puzzle. This unfamiliarity makes it harder to read and follow, particularly when juggling multiple coding paradigms.

A few words in defence

Don't get me wrong. Ramda, despite the concerns I raised earlier, is undeniably a powerful tool when appropriately applied.

It really shines when a project is fully committed to functional programming. Now, if your project is more like a mixed bag, bouncing between different coding styles, going with JavaScript and Lodash might be a more comfortable path. Remember, the goal here isn't to use the fanciest tools, but to write clear and efficient code.

JavaScript has been working out, and it's been getting better at handling the functional style without breaking a sweat. Lodash, too, has proven its worth as a versatile tool, capable of a lot but without any unnecessary fuss.

Keeping a consistent coding style across your project can also be beneficial. It brings a sense of unity, making it easier for anyone new to the team to get on board quickly. The quicker they adapt, the sooner they can contribute.

Conclusion

While Ramda definitely has its strengths and holds a significant place in the realm of functional programming, it might not always be the best fit, particularly if your project uses various coding styles. In such situations, you might find that sticking with the modern native JavaScript and Lodash serves you better. Ultimately, our aim is to write code that's both effective and clear, rather than just employing sophisticated tools for their own sake.

Photo by Ryunosuke Kikuno on Unsplash

Top comments (0)