6 Reasons Why I Prefer FxTS Over Lodash or Ramda


Intro

Today, as someone who loves functional programming, I’m going to introduce a functional library FxTs that I’m sure you’ll never know. A few years ago I stumbled upon an online functional programming course authored by “Indong Yoo” who created FxJs(previous version of FxTs) taught functional programming while creating a functional library from scratch.

Thanks to this chance, I came to understand the goals and principles of FxTs, and I was fascinated by the beauty of functional programming 😍. After that, I started to use actively this library for my personal vue2/vue3 projects and it resulted in my code being more readable and easier to maintain.

For this reason, I’m thinking of using this library for one of my projects which gonna be migrated soon from nuxt2 to nuxt3 because vue3’s composition API is matched well with the functional programming patterns.

Wait, what is functional programming?

Functional programming is a paradigm to create clean, readable code by combining small functions with no side effects.

With functional programming, we can replace the imperative programming approach with code that is more readable and maintainable. For example, let’s say you gonna create a function getMalesAge that selects only n males from the user list and gets the sum of their ages.

javascript

// Make function `getMalesAge` that selects `n` males and returns the sum of their ages.
const users = [
  { name: "David", age: 10, gender: "male" },
  { name: "Flora", age: 24, gender: "female" },
  { name: "Lucy", age: 8, gender: "female" },
  { name: "Tom", age: 30, gender: "male" },
  ...
]

// Imperative style
function getMalesAge(n, users) {
  let sumAge = 0;
  let count = 0;

  for (const user of users) {
    if (user.gender === "male") {
      sumAge += user.age;
      count++;
    }
    if (count === n) break;
  }
  return sumAge;
}

// Functional style
const sum = (a, b) => a + b;
function getMalesAge(n, users) {
  return pipe(
    users,
    filter(user => user.gender === 'male'),
    take(n),
    map(user => user.age),
    reduce(sum)
  )
}

What we need to pay attention to here is that loop statements (for, while), conditional statements (if), and control statements (break) can be written by functions ONLY. We can write the code more declaratively rather than do it indicating how sumAge or count variables are changed.

The above functional style example was written using FxTs. Those functions such as map, filter, reduce and take have already been curried as the most functional libraries did. The usage of functional libraries is generally similar, so once you know one, you can quickly use the other.

There are famous functional libraries such as lodashFP and Ramda, but I highly recommend “FxTs” for the following reasons.

Why I recommend you FxTs and not LodashFP, Ramda?

1. Lazy evaluation support

Try this link: FxTs vs LodashFP vs Ramda performance comparison

First of all, all the functions in FxTs perfectly support the ES6 specification iterator protocols. In other words, It delays the creation of the collection until the last and evaluates it at the end. This brings performance advantages that other libraries do not have, such as lodashFP or Ramda.

If you actually measure the performance, you can see that FxTs is significantly faster than Ramda and slightly faster than Lodash. This difference becomes larger as the size of the array increases.

Performance test Ramda vs lodash vs FxTs

javascript

// [1, 2, 3, ..., 99999]
const data = new Array(100000).fill(0).map((_, idx) => idx);

// Ramda
R.pipe(
    R.filter(isOdd), // [1, 3, 5, ..., 99999]
    R.map(square), // [1, 9, 25, ..., 9999800001]
    R.filter(lessThanThreeDigits), // // [1, 9, 25, ..., 999]
    R.take(3) // [1, 9, 25]
)(data);

// FxTs
Fx.pipe(
    data,
    Fx.filter(isOdd),
    Fx.map(square),
    Fx.filter(lessThanThreeDigits),
    Fx.take(3), // [1, 9, 25]
    Fx.toArray
);

Why there is such a big difference? To investigate it, let’s insert tap function in the middle to know how FxTs works. The tap function is also implemented in Ramda and it’s used when you want to check how the value changes in the middle of function composition.

javascript

// Create a big array
const data = new Array(100000).fill(0).map((_, idx) => idx);

// Ramda
R.pipe(
  R.filter(isOdd),
  R.tap(console.log), // [1, 3, 5, ..., 99999]
  R.map(square),
  R.tap(console.log), // [1, 9, 25, ..., 9999800001]
  R.filter(lessThanThreeDigits),
  R.tap(console.log), // // [1, 9, 25, ..., 999]
  R.take(3)
  R.tap(console.log), // [1, 9, 25]
)(data);

// FxTs
Fx.pipe(
  data,
  Fx.filter(isOdd),
  Fx.tap(console.log), // {next: ƒ (), throw: ƒ (), return: ƒ ()}
  Fx.map(square),
  Fx.tap(console.log), // {next: ƒ (), throw: ƒ (), return: ƒ ()}
  Fx.filter(lessThanThreeDigits),
  Fx.tap(console.log), // {next: ƒ (), throw: ƒ (), return: ƒ ()}
  Fx.take(3),
  Fx.toArray,
  Fx.tap(console.log) // [1, 9, 25]
);

As you can see that FxTs outputs an iterator, unlike Ramda which outputs an array. That means, Ramda creates an array whenever map, filter are executed, which is why Ramda’s performance is significantly lower than FxTs.

You might notice that there is toArray in FxTs at the last step of function composition. It helps to convert an iterator into an array.

If you want to retrieve the actual value inside the iterator for debugging purposes, you can use the peek function in the middle of piping in FxTs.

However, there is something to be aware of: lazy evaluation is not always performant. If we remove take of the above example, the results are reversed.

javascript

 // [1, 2, 3, ..., 99999]
const data = new Array(100000).fill(0).map((_, idx) => idx);

// Ramda
R.pipe(
    R.filter(isOdd), // [1, 3, 5, ..., 99999]
    R.map(square), // [1, 9, 25, ..., 9999800001]
    R.filter(lessThanThreeDigits), // // [1, 9, 25, ..., 999]
    // R.take(3) // [1, 9, 25]
)(data);

// FxTs
Fx.pipe(
    data,
    Fx.filter(isOdd),
    Fx.map(square),
    Fx.filter(lessThanThreeDigits),
    // Fx.take(3), // [1, 9, 25]
    Fx.toArray
);

Performance test Ramda vs lodash vs FxTs 2

This is because toArray function that converts an iterator into an array takes a lot of time. Lazy evaluation is good when you need to perform an operation by extracting only a part of a lot of array data. Otherwise, it is better to use with native JS Array API.

Try this link: FxTs vs LodashFP vs Ramda performance comparison2

javascript

console.log("3. FxTs");
timer.start();
Fx.pipe(
  data,
  Fx.filter(isOdd),
  Fx.map(square),
  Fx.filter(lessThanThreeDigits),
  Fx.toArray
);
timer.end();

console.log("4. FxTs with vanilla");
timer.start();
Fx.pipe(
  data,
  (arr) => arr.filter(isOdd),
  (arr) => arr.map(square),
  (arr) => arr.filter(lessThanThreeDigits)
);
timer.end();

Performance test Ramda vs lodash vs FxTs vs FxTs with vanilla

Therefore, it is important to know at which moment lazy evaluation can achieve maximum performance.

If you want to know more about how does iterator protocol works with FP, I recommend you to read this article.

2. Easy async control

Try this link: asynchronous test Fxts vs LodashFP vs Ramda

FxTs has a super convenient way to deal with async/sync functions. You can easily implement Promise.all in the form of functional programming, and furthermore, it is possible to control how many functions are executed using the Concurrency feature.

LodashFP and Ramda do not support asynchronous functions in the library itself. This is because it is not designed to receive a Promise object when creating a library in the first place. If the code below is executed with lodashFP or Ramda, the Promise object remains in the array.

javascript

// Async with Ramda example
await R.pipe(
        R.map((a) => new Promise((resolve) => resolve(a))),
        R.filter((a) => new Promise((resolve) => resolve(a % 2))),
        R.take(3) // [Promise, Promise, Promise]
)([1, 2, 3, 4, 5])

However, FxTs internally check whether it is a Promise object through instanceof when retrieving a value from an array through an iterator. If the value is a Promise, FxTs retrieves the value through then.

javascript

// Async with FxTs example
await Fx.pipe(
  [1, 2, 3, 4, 5],
  Fx.toAsync, // convert iterator into asyncIterator
  Fx.map((a) => new Promise((resolve) => resolve(a))),
  Fx.filter((a) => new Promise((resolve) => resolve(a % 2))),
  Fx.take(3) // [1, 3, 5]
  Fx.toArray,
)

For this reason, when using other libraries than FxTs, functions must be implemented by yourself to check Promise as follows.

javascript

// Async with Ramda example 2
// Create your own filter to unwrap Promise
const filterP = (f) => async (asyncArr) => {
  const arr = [];
  for await (const a of asyncArr) {
    if (f(a)) arr.push(a);
  }
  return arr;
};

await pipe(
  R.map((a) => new Promise((resolve) => resolve(a))),
  filterP((a) => a % 2) // [1, 3, 5]
)([1, 2, 3, 4, 5])

FxTs also has a very cool feature Concurrency. In the case of the above example, 5 asynchronous functions are executed simultaneously like Promise.all but if you use the concurrent function, you can control how many asynchronous functions are executed at the same time.

javascript

const fetchApi = (page) => new Promise((resolve) => setTimeout(() => resolve(page), 1000));

await pipe(
    [1, 2, 3, 4, 5],
    toAsync,
    map(fetchApi), // fake fetch
    concurrent(2),
    each(console.log)
);

fxts concurrency

3. Error handling

Try this link: Error handling test FxTs vs LodashFP vs Ramda

Handling errors well is directly related to the robust program. In FxTs, all errors can be handled using try catch

For example, assuming that there is null in the middle of the data array where the object should exist. When accessing the null property in Ramda and lodashFP, the corresponding error does not go to try catch and the library itself breaks down.

javascript

(async () => {
  try {
    await R.pipe(
      R.map((obj) => new Promise((resolve) => resolve(obj.val + 10))),
      R.filter((a) => a % 2),
      R.take(3),
      R.reduce((a, b) => a + b)
    )([{ val: 1 }, { val: 2 }, null, { val: 4 }, { val: 5 }]);
  } catch (error) {
    console.log('1. Ramda', 'error catch'); // this line is not executed.
  }
})();

4. Easy to use

If you have the basic knowledge of functional programming and understand the iterator protocol, you can start functional programming with FxTs. Since it is designed to implement only for the data type in JS without implementing Functor or Monad separately, it does not require a deep understanding of the relevant library or functional programming, so it is perfect for an FP beginner.

You just need to understand 2 basic concepts in FxTs: Lazy and Strict.

  • Lazy function uses lazy evaluation, returns an iterator, and is used at the beginning or in the middle of function composition.
  • Strict function uses immediate evaluation, returns a value, and is used at the end of function composition.

Let’s take an example.

javascript

Fx.pipe(
  [1, 2, 3, 4, 5],
  map((a) => a * 2), // Lazy (return iterator)
  filter((a) => a % 2), // Lazy (return iterator)
  toArray // Strict (return array)
)

Fx.pipe(
  [1, 2, 3, 4, 5],
  map((a) => a * 2), // Lazy (return iterator)
  filter((a) => a % 2), // Lazy (return iterator)
  reduce((a, b) => a + b) // Strict (return number)
)

Fx.pipe(
  [1, 2, 3, 4, 5],
  map((a) => a * 2), // Lazy (return iterator)
  filter((a) => a % 2), // Lazy (return iterator)
  each(console.log) // Strict (print console)
)

Lazy functions such as map, filter are used at the beginning or middle of function composition, and Strict functions are used at the last to make meaningful value. You can see the list of Lazy and Strict functions here.

5. Lightweight

FxTs consists of only functions essential for functional programming, so it is suitable for an introduction to functional programming.

It boldly excludes monads and functors that do not exist in Vanilla JavaScript and aims for lightweight functional programming only with basic JavaScript types and Promise. It supports tree-shaking of course.

6. Promising opensource

FxTs has recently been registered in awesome-javascript and it is getting popular. Of course, it is not yet as popular as LodashFP or Ramda, but it is worthy of being widely used considering the above reasons I have mentioned so far.

Conclusion

FxTs is an open-source library developed by marpple dev team, and its popularity is increasing in Korea thanks to developer Indong Yoo’s functional programming lecture.

For those who are interested in FxTs through this article, I have linked the Slack community as a reference. It’s still a fresh library, so if you’d like to contribute, maybe now is a chance

Reference