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
- Higher-order functions
- Underscore/Lodash
- Chaining
- Partial Application
- The good stuff!
- Currying
- Composition
- ...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