Next-level Functional Javascript

With Ramda

Warren Seymour

Lead Developer, radify.io

 

warren@radify.io

 

@woogoose

DISCLAIMER

  • This is a new way of thinking
  • You may find yourself feeling stupid
  • Don't be afraid to ask questions!
  • Be prepared to rethink assumptions

Functional Enlightenment

  1. Higher-order functions
  2. Underscore/Lodash
    • Chaining
    • Partial Application
  3. The good stuff!
    • Currying
    • Composition
  4. ...more!

Higher-order functions

forEach([4, 8, 15, 16, 23, 42], function(n) {
  console.log(n + 1);
});

// 5 9 16 17 24 43

(Functions as arguments to other functions)

function forEach(array, fn) {
  for(var i = 0; i < array.length; i++) {
    fn(array[i]);
  }  
}

Underscore/Lodash

var numbers = [4, 8, 15, 16, 23, 42];

_.map, _.reduce and friends

_.map(numbers, function(n) {
  return n * 2;
}); // => [8, 16, 30, 32, 46, 84]
_.reduce(numbers, function(sum, n) {
  return sum + n;
}, 0); // => 108
_.filter(numbers, function(n) {
  return n > 10;
}); // => [15, 16, 23, 42]

Chaining

_(numbers)

_.chain(value)  ▸  operations  ▸  .value()

  .filter(function(n) {
    return n > 10;
  })
  .reverse()
  .map(function(n) {
    return n * 2;
  })
  .value();

// => [84, 46, 32, 30]

Chaining

Powerful, useful, oh-so slightly lacking

  • Values must always be _(wrapped) first
  • Result must be extracted with .value()
    • ... most of the time
  • Requires function with explicit parameters
  • Best implementation of a flawed concept

Partial Application

function greaterThan(compare, number) {
  return number > compare;
}

_.partial(fn, arguments)

_.filter(numbers, greaterThanTen);
// => [15, 16, 23, 42]
var greaterThanTen = _.partial(greaterThan, 10);

Partial Application

Inflexible

  • Define parameters at time of _.partial
  • Good concept, wrong approach

Enter Ramda

ramdajs.com

  • Functional toolbelt like _ or _
  • Immutable data manipulation
  • Authentic Curry(ing)
  • Browser & Node compatible
  • REPL: http://ramdajs.com/repl

Data Last

R.map(function(n) {
  return n * 2;
}, numbers); // => [8, 16, 30, 32, 46, 84]

map(fn, data);

R.reduce(function(sum, n) {
  return sum + n;
}, 0, numbers); // => 108
R.filter(function(n) {
  return n > 10;
}, numbers); // => [15, 16, 23, 42]

Currying

  • Named after Haskell Brooks Curry
    • American mathematician
    • 1900 - 1982
    • *That* language
  • f(x, y) ▸ f(x)(y)

Currying

var add = function(a, b) {
  return a + b;
};
add(4, 5); // => 9
var cAdd = R.curry(add);

var addFourTo = cAdd(4);
addFourTo(5); // => 9

aka: a better _.partial

cAdd(4)(5); // => 9
cAdd(4, 5); // => 9

currying

[glencurry like a glenboss]

always(be)(currying)

var times = R.curry(function(a, b) {
  return a * b;
});

R.map(times(2), numbers)

// => [8, 16, 30, 32, 46, 84]

map(c(x), data)

always(be)(currying)

Ramda functions are auto-curried!

R.map(fn, data)

var mapTimes2 = R.map(times(2));

mapTimes2(numbers);

Why Curry helps

  • Build many small logic blocks
    • Declarative
    • Predictable
    • Re-usable
  • Glue them together
    • Sometimes without a function() in sight

Composition

function firstToUpper(string) {
  return R.head(R.toUpper(string));
}

firstToUpper('abc'); // => 'A'
f(g(x)) ▸ c(f, g)(x)
var firstToUpper = R.compose(R.head, R.toUpper);

firstToUpper('abc'); // => 'A'

Composition

f(g(x)) ▸ c(f, g)(x)
  • Reads left-to-right
  • Calls right-to-left
    • Last function is called first
    • First function yields return value
  • Try R.pipe
    • f(g(x))  p(g, f)(x)

Composition

f(g(x)) ▸ c(f, g)(x)
  • Declarative
  • No need for glue function and parameters
  • Compose Composed functions!
  • Think about data flow

Composition & Currying

var pipe = R.pipe(
  R.filter(R.lt(10)),
  R.reverse,
  R.map(R.multiply(2))
);

pipe(numbers)

// => [84, 46, 32, 30]

[whoa]

Immutability

  • Strive for pure functions
    • Same input === same output
    • Destroy all context!
  • Don't touch my stuff!
    • Don't mutate inputs
    • Return new objects
  • Trade memory for predicability
    • RAM is cheap, your sanity is not

I'm looking at you, _.extend!

_.extend

R.merge

var source = { foo: 'bar' };

var dest = _.extend(source, {
  baz: 'qux'
});

console.log(dest);
// { foo: 'bar', baz: 'qux' }

console.log(source);
// { foo: 'bar', baz: 'qux' }
var source = { foo: 'bar' };

var dest = R.merge(source, {
  baz: 'qux'
});

console.log(dest);
// { foo: 'bar', baz: 'qux' }

console.log(source);
// { foo: 'bar' }

Returns merged object and mutates source

Does not mutate source

_.extend({}, source, dest)
// bleugh!

R.assoc

var source = {foo: 'bar'};

var dest = R.assoc('baz', 'qux', source);

console.log(dest);
// { foo: 'bar', baz: 'qux' }

console.log(source);
// { foo: 'bar' }

@see R.assocPath

R.append

var source = [4, 8, 15];

var dest = R.append(16, source);

console.log(dest);
// [4, 8, 15, 16]

console.log(source);
// [4, 8, 15]

What's Next?

  • Go play!
  • Build something new
  • Learn a new language (?)

Resources

  • ramdajs.com
  • hughfdjackson.com/javascript/why-curry-helps
  • learnyouahaskell.com
    • Not a prerequisite
    • May expand your horizons and understanding

Questions

Next-level Functional Javascript With Ramda

By Warren Seymour

Next-level Functional Javascript With Ramda

  • 2,471