Writing Fluent Functional Code

Written by hesher | Published 2020/05/13
Tech Story Tags: javascript | functional-programming | fluent-design | fluent-code | fucntional-coding | fluent-api | hackernoon-top-story | fluent-functional-code

TLDR The most straightforward functional approach is using `pipe()` with stateless setters. This pattern doesn’t allow you chaining, but some of us need chaining to feel comfortable in reading code. The idea here is that the expression builder is based on a curried function that accepts a path defined by lens. value: value: the value to write to the path defines by lens, value: set will return a new object (never mutate) based on the source object with the new property and value.via the TL;DR App

Fluent code should make you feel happy, like this image does.
Fluent Api is code that reads as a sentence. You probably know this if you ever a wrote test code, it reads as a sentence:
expect(someValue).to.equal(expectedValue)
If you’ve used an expression builder, you probably noticed it is very easy to read:
const theSite = new SiteBuilder()
  .withSiteName('a Site')
  .withDomain('www.some-domain.com')
  .build();
To enable this API, you can use a
class
that returns
this
from each of it’s methods:
class SiteBuilder {
  private site: any;
  withSiteName(name: string) {
    this.site.name = name;
    return this;
  }

  withDomain(domain: string) {
    this.site.domain = domain;
    return this;
  }

  build() {
    return this.site;
  }
}
Pretty straightforward, and actually easy to implement but it has it’s drawbacks — having to return
this
from every method is kind of annoying.
If you’re like me, and you dislike using
this
, you may have tried looking for alternatives.
The most straightforward functional approach, and in my opinion the best one is using `pipe()` with stateless setters. I’ll use ramda to introduce it:
const withSiteName = set(lensProp('name'));
const withDomain = set(lensProp('domain'));

const site = pipe(withSiteName('a Site'), withDomain('domain'));
site({}); // site is the function that returns the value
The idea here is that
set(lens, value, src)
is a curried function that accepts a
  • lens
    (basically a way to define a path), I’ll explain a little bit on this in the next paragraph.
  • value:
    the value to write to the path defined by lens
  • src:
    set will return a new object (never mutate) based on the source object with the new property and value.
In the end, this has the same result as a class builder but is more concise and no need to deal with redundant
this
.
A couple of notes on this approach:
  1. Not really useful with Typescript right now since curried functions require the use of variadic kinds and Typescript, currently, doesn’t support it. It’s on the roadmap though. You can read here to understand the problem.
  2. If you need to write to a path that is deeper than a single level, for (a contrived) example site.services.securityEnabled, you should use lensPath:
const withSecurityEnabled = set(lensPath([‘services’, ‘securityEnabled’]))
Ok, so if you’re reading this you may have noticed that, although it looks great, this pattern doesn’t allow you chaining and while I propose that the definition of fluent should be expanded or put in a broader context, some of us need chaining to feel comfortable in reading code.
Let’s see how we keep the functional aspects of the expression builder while allowing to chain.
So this is a simple example of an expression builder that’s based on functional:
const SiteBuilder = value => ({
  withSiteName: name => SiteBuilder({...value, ...{name}}),
  withDomain: domain => SiteBuilder({...value, ...{domain}}),
  build: () => value
});

const site = SiteBuilder({})
  .withSiteName('hello')
  .withDomain('some-domain')
  .build();
It’s still easy to read, starting to look similar to the class version but without the need to return
this
from every method.
There is some duplication, still, but we can remove it with some simple refactoring:
// Helper Functions
const wrapWith = wrapper => obj => key => val => wrapper({...obj, [key]: val});
const wrapWithSiteBuilder = wrapWith(SiteBuilder);

// Expression Builder
function SiteBuilder(value) {
  const siteBuilderWrapper = wrapWithSiteBuilder(value);
  return {
    withSiteName: siteBuilderWrapper('name'),
    withDomain: siteBuilderWrapper('domain'),
    build: () => value
  };
}

SiteBuilder({}).withSiteName('hello').withDomain('some-domain').build();
It has it’s pros and cons and I’m sure it can be made to look nicer. (Feel free to suggest)
But the bottom line is — It’s a stateless, functional, fluent, expression builder.
WDYT?

Published by HackerNoon on 2020/05/13