3 min read

Why the heck did I bother making fluent-sort?

I made a library called fluent-sort, and I really like it, even though it's a little useless. As developers, self-expression is important, and sometimes that's enough.
Why the heck did I bother making fluent-sort?

A few weeks ago, I got annoyed sorting an array.

It wasn't that the array sorting wasn't fast enough, but rather that the API for sorting isn't necessarily as developed as in other languages. I am of the opinion that JavaScript's [].sort is a little dated; it sorts an array in place, and the function that you pass has to return 0, -1, or 1 and you have to recall which one to use for which direction.

There's nothing necessarily wrong with [].sort, but it's 2018, and we can do a little better than that.

In my case, I had to emulate C#'s .OrderByDescending(x => x.FieldA).ThenBy(y => y.FieldB) based on user input; to further complicate things, the users could select each field to be either ascending or descending.

class Test {
  public int FieldA { get; set; }
  
  public int FieldB { get; set; }
  
  public int FieldC { get; set; }
  
  public Test(int a, int b, int c) {
      this.FieldA = a;
      this.FieldB = b;
      this.FieldC = c;
  }
}

var testA = new Test(2, 2, 2);
var testB = new Test(1, 2, 3);
var testC = new Test(2, 3, 1);
var list = new List<Test>() { testA, testB, testC };

var orderedList = list.OrderByDescending(x => x.FieldA).ThenBy(x => x.FieldC); //testC, testA, testB

I write a lot of JavaScript in my day to day development, which consists primarily of creating single page applications. In 2018, this means that the code I write lately deals with organizing data and handling user interactions.

I'm a big fan of fluent interfaces and pure methods in code. JavaScript's [].filter, [].map, and [].reduce are all pure methods that don't manipulate the original base array but rather return new results.

I designed fluent-sort emulate a fluent interface. It is not a literal fluent interface, in that it returns new instances with each method chained on it, in order to be more closely aligned with the behavior of JavaScript's native pure array methods.

My decisions for developing fluent-sort were pretty numerous:

  1. I felt that JavaScript was lacking decent sort-by-field notation
  2. I felt that JavaScript's notation for sorting descending vs. ascending was lacking.
  3. I think that arrays in JavaScript are misaligned; some methods are pure, some are in place, and there are almost always fewer side effects from pure code than from code that mutates state.
  4. Our biological clocks are ticking in one direction towards our inevitable death and entrance into the void and this made me happy for a little while as I created it
  5. I think there's a large need for utilities that don't have dependencies of their own, that way they are easier to audit and maintain.

I'm planning on maintaining two forks of fluent-sort in the near future:

  1. Releasing fluent-sort v2.0.0, which will mutate an object by configuring through the same fluent-api, however no longer create new objects with each method
  2. Releasing "pure-sort" v1.0.0, which will be the current implementation of fluent-sort but re-branded for purity.

I'm also intending on testing out Gitbooks to make one of those snazzy documentation pages!

I planned out fluent-sort to emulate the C# syntax. Internally, it uses the native JavaScript array sorting to maintain functional parity with the language, and because you can rarely beat the performance of native methods. Fluent-sort acts as a wrapper that creates the sorting function passed to array.sort based on the configuration provided through the library's API.

const testCases = [
    {
      id: 0,
      name: "Strong Monster",
      strength: 10,
      agility: 5,
      intelligence: 8,
      monsterdexOrder: 5
    },
    {
      id: 1,
      name: "Fast Monster",
      strength: 5,
      agility: 10,
      intelligence: 5,
      monsterdexOrder: 1
    },
    {
      id: 2,
      name: "Mediocre Monster",
      strength: 7.5,
      agility: 7.5,
      intelligence: 8,
      monsterdexOrder: 6
    },
    {
      id: 3,
      name: "Unimpressive Monster",
      strength: 2,
      agility: 2,
      intelligence: 2,
      monsterdexOrder: 4
    },
    {
      id: 4,
      name: "Slow Monster",
      strength: 7.5,
      agility: 3,
      intelligence: 8,
      monsterdexOrder: 17
    },
    {
      id: 5,
      name: "Smart Monster",
      strength: 3,
      agility: 7.5,
      intelligence: 15,
      monsterdexOrder: 75
    }
  ];

const sortedTests = fluentSort(testCases) // Returns the OrderableInitiator
    .sortByField(x => x.intelligence) // Returns the Orderable
    .thenByFieldDescending(y => y.agility) // Returns a new Orderable with both rules applied
    .result(); // Evaluates and returns the result

console.log(sortedTests.map(x => x.name));

Would log:

[ 'Unimpressive Monster',
  'Fast Monster',
  'Mediocre Monster',
  'Strong Monster',
  'Slow Monster',
  'Smart Monster' ]

I'm sure that fluent-sort has areas that can be improved. Issues and pull requests are always welcome!