Deedo's tips for FP with JS

Douglas Crockford was one of the earliest to say that, JS has good and bad parts and using only a subset of the features, actually makes the language more pleasant to use. He dubbed it "The Good Parts".

Software is often deemed better the more it can do. However with languages sometimes that is not true. Elm also follows the principle of making things better by removing things and focusing on good ways to do it rather than many ways to do it. While JS certanly can do a lot, I am a strong believer that it is better to pick a certain paradigm of programming and then stick to a couple of features that let you get the job done in an efficient way. So here are my "Good Parts of functional JS", which I hope will guide you in your functional journey.

Don't use let and use const instead.

With the arrival of ES6 the var keyword got some new buddies named let and const. Using const is great because it makes you program very simple, since you can assign const variables only once. With let or var that isn't the case:

const val = 3; // will always be three

let val2 = 6; // can be reassigned
var val3 = 6; // can be reassigned

With const you never have to check if that reference it changed, which makes the program very easy and straighforward.

Avoid statements and try to use expressions

This one goes a long way. The difference between a statement and an expression is that an expression can always be evaluated to a single value. Just use this example about two ways to create a conditional.

Use the ternary operator over an if

// the expression is evaluated and gives you a value.
const val = someCondition ? 3 : 6;

// with a statement you cannot use a constant
let val;
if (someCondition) {
  val = 3;
} else {
  val = 6;
}

Avoid loops, use functions and recursion

Use arrow functions and currying

As you probably know there are two kinds of functions in JS. The ones which are created with the function keyword and the ES6 addition arrow functions.

So why shouldn't you use the former? Those functions are more suited to do OOP. They define a this which points to the object on, which the method was called.

const obj = {};
obj.someMethod = function() { console.log(this); };
obj.someMethod(); // logs `this`, which points to `ob`
someMethod(); // no object here, this points to the `window` or global object

Since we want be pure in FP, we do not want that reference to this, using it breaks the purity by which a functions output must solely depend on it's arguments.

It's probably best to avoid this altogether. Using arrow functions is a good practice that helps that cause because they don't define their own this. Unfortunately you can still use this in them, but it will simply be the same reference as the one of the outer scope.

const obj = {};
obj.someFunction = () => this;
obj.someFunction(); // returns `this`, which points to `window` or global object
someFunction(); // also returns `this`, which points to `window` or global object

Currying and partial function application

  • Haskell Curry origins
  • simple arrow currying
  • generic curry functions

Use these 2 terms in sentence: Okay I got it. Currying is a process, to transform a function into another function which can be partially applied.

So far so good. But what is partial function application? Most JS developers are used to passing all arguments to a function at once. But in some FP languages (i.e. Elm, Haskell) it is okay to pass only some of the arguments and get a function back that accepts the remaining arguments. This concept is also used by Redux's connect function for example.

import { connect } from 'redux';

...

const connectedComponent = connect(mapStateToProps)(MyComponent);

connect accepts some mapping function and returns another function whcih is then called with the component which then returns the final component which is connected to the redux store.

Let's look at another example.

const add = a => b => a + b;

const add3 = add(3);

add3(10); // 13

We easily created a curried function just by using arrow functions.

However, this means also means that if you do want to pass all arguments at the same time you have to do this add(3)(10) which most people would fine annoying and this also requires you to know which function is curried in which way and so on.

Let's fix this. We can use JS to create a function, which curries other functions in just a couple of lines.

const curry = (fn, arity = fn.length) => {
  const resolver = (...args) => (...innerArgs) => {
      const local = [...args, ...innerArgs];
      return local.length >= arity ? fn(...local) : resolver(...local);
  };
  return resolver();
};

The resulting function will check how many arguments are passed and return another function expecting the remaining arguments or the final result if all arguments where there. Let's do this again with add.

const add = (a, b) => a + b; // not a curried function
const curriedAdd = curry(add);

curriedAdd(3, 6) // 9, works
curriedAdd(3)(6) // 9, also works

Usually Function.prototype.length will give the correct number of arguments that a function will accept. But because some functions in JS have optional arguments or are use rest arguments, sometimes the arity to which the function should be curried needs to be passed explicitly.

// add takes two arguments
const add = (a, b) => a + b;
add.length; // 2
myAdd = curry(add); // the length property is fine is this case as is used for the arity by default
myAdd(2)(4); // 6

// fetch takes 2 arguments but the second one is optional
fetch.length // 1,

// if we want to use the second argument we have to curry, passing the arity explicitly
const myFetch = curry(fetch, 2);
fetch('some.url')({ ... }); // works

// Array.of takes an indefinite number of args
Array.of(2, 4, 4) // [2, 4, 4]
Array.of.length // 0

const tuple = curry(Array.of, 2);
tuple('a')('b') // ['a', 'b']

Ramda's function are all precurried so you can call them passing all arguments or only a couple at a time.

Modelling data accurately

results matching ""

    No results matching ""