On Github wilhelmson / programming-with-functions
each argument maps to exactly one value
always
they take arguments or not
they return values or not
they can mutate state, or rely on hidden state
they may not have a clear domain or codomain
they can "launch the missles"
are awesome and weird
functions are first-class members
functions are objects
this
highly specific
highly imperative
not really like functions at all
like, lots more
use simple data structures
use simple functions
takes in arguments and returns values
uses no hidden state
has no side effects
is referentially transparent
but most importantly pure functions are...
Constructs we take for granted can be abstracted into functions
if, not, and, or, for loops, existence, indexability
function prop(name,obj){
return obj[name];
}
function not(val){
return !val;
}
because little, general, pure functions are
var stooges = [{name: 'curly', age: 25}, {name: 'moe', age: 21}, {name: 'larry', age: 23}];
var youngest = _.chain(stooges)
.sortBy(function(stooge){ return stooge.age; })
.first()
.value(); //=> { age: 21, name: "moe" }
I've been told there is a simpler way, but it's devastating to my point
var stooges = [{name: 'curly', age: 25}, {name: 'moe', age: 21}, {name: 'larry', age: 23}];
compose(first, sortBy(prop("age")))(stooges); //=> { age: 21, name: "moe" }
compose(h, g, f)(a) === h(g(f(a)))
function add(a){
return function(b){
return a + b;
};
}
add(1)(2) //=> 3 (pause for effect)
we delay the application of some arguments
var add10 = add(10);
[1, 2, 3].map(add10); //=> [11, 12, 13];
var stooges = [{name: 'curly', age: 25}, {name: 'moe', age: 21}, {name: 'larry', age: 23}];
compose(first, sortBy(prop("age")))(stooges); //=> { age: 21, name: "moe" }
functions in and/or functions out
[a] → (a → b) → [b]
[1, 2, 3].map(
function(n){
return n * 2;
}); //=> [2, 4, 6]
[a] → (a → bool) → [a]
[1, 2, 3].filter(
function(n){
return n % 2 == 0;
}); //=> [2]
[a] → (b → a → b) → b → b*
[1, 2, 3].reduce(
function(acc, n){
return acc + n;
}, 0); //=> 6
*In JavaScript the accumulator is optional, but it shouldn't be
[a] → (a → b) → [b]
[a] → (a → bool) → [a]
[a] → (b → a → b) → b → b
which means they're not composable
fun with reduce
//+ (b → a → b) → b → [a] → b
function reduce(f){
return function(b){
return function(xs){
return xs.reduce(f,b);
};
};
}
there are two types of folds left/right
on the surface the difference is direction, but it's more than that
In other languages, the order of arguments in the xform is different, which can have a huge impact
map, filter, forEach
function sum (xs){
var sum = 0;
for(var i=0, len = xs.length; i < len; i++){
sum += xs[i];
}
return sum;
}
-or-
var sum = reduce(add)(0);
var allTrue = reduce(and)(true)
function and(a, b){
return a && b;
}
var anyTrue = reduce(or)(false)
function or(a, b){
return a || b;
}
functions in and functions out
//+ (b → c) → (a → b) → a → c
function compose(g, f){
return function(a){
return g(f(a));
};
}
flip
//+ (a → b → c) → b → a → c
function flip(fn){
return function(b){
return function(a){
return fn(a, b);
}
}
}
//+ (a → b) → [a] → [b]
var betterMap = flip(_.map); //because we're all using Underscore/Lo-Dash
betterMap(function(x){ return x * x; })([1,2,3]); //=> [1, 4, 9];
//or
//+ [a] → [b]
var squares = betterMap(function(x){ return x * x; }); //=>[Function]
map(parseInt, ['1', '2', '3']); //=>[1, NaN, NaN]
var safeParseInt = flip(parseInt)(10);
map(safeParseInt, ['1', '2', '3']); //=> [1, 2, 3];
Logging
function superAwesomeLogger(){
console.log([].slice.call(arguments));
}
function add(a,b){
return a + b;
}
//+ (a → b) → (a → c) → a → c
function before(decorator){
return function(target){
return function(){
decorator.apply(this, arguments);
return target.apply(this, arguments);
}
}
}
//+ (a → b) → (a → c) → a → c
function after(decorator){
return function(target){
return function(){
var val = target.apply(this, arguments);
decorator(val);
return val;
}
}
}
var logBefore = before(superAwesomeLogger);
var logAfter = after(superAwesomeLogger);
var logAddArgs = logBefore(add);
logAddArgs(1,2); //=> 3 and logs [1,2] to the console.
var logAddResult = logAfter(add)
logAddResult(1,2); //=> 3 and logs [3] to the console.
var logBeforeAndAfter = compose(logAfter, logBefore);
var superAwesomeAdder = logBeforeAndAfter(add);
superAwesomeAdder(1,2) //=> 3 and logs [1,2] and [3] to the console.
curry
function curry2(f){
return function(a){
return function(b){
return f(a, b);
}
}
}
function curry(fn, fnLen){
fnLen = fnLen || fn.length;
return function curriedFn(){
var args = [].slice.call(arguments);
if (args.length >= fnLen) return fn.apply(this, args);
return function _curriedFn_(){
var newArgs = [].slice.call(arguments);
return curriedFn.apply(this, args.concat(newArgs));
}
}
}
var sumArgs = compose(sum, toArgs);
var curry2 = flip(curry)(2);
var curry3 = flip(curry)(3);
var add2 = curry(sumArgs);
var add3 = crry(sumArgs);
add2(1); //=> [Function]
add3(1, 2); //=> [Function]
add2(1)(2); //=> 3
add2(1,2); //=> 3
add3(1)(2)(3); //=> 6
add3(1, 2, 3); //=> 6
guarding against null & undefined
var maybe = curry(function(f, a){
return a == null ? void 0 : f(a);
});
function sq(x){
return x * x;
}
var safeSq = maybe(sq);
sq(); //=> Nan
sq(undefined); //=> Nan
sq(null); //=> 0 because JavaScript
safeSq(undefined); //=> undefined
safeSq(null); //=> undefined
safeSq(); //=> [Function] because maybe is curried
var provided = curry(function(guard, fn){
return function(){
return guard.apply(this, arguments) ? fn.apply(this, arguments) : void 0;
}
});
var maybe = provided(function(val){
return val != null;
});
var maybe = curry(function(fn, val){
return val != null ? fn(val) : void 0;
});
var maybe = provided(function(val){
return val != null;
});
function not(fn){
return function(){
return !fn.apply(this, arguments);
}
}
function isNothing(val){
return val == null;
}
var except = compose(provided, not);
var maybe = except(isNothing);
pluck
taken from JavaScript Combinators by Reg Braithwaite
var pluck = curry(function(key, objs){
return objs.map(function(obj){
return obj[key];
});
});
var pluck = curry(function(key, objs){
return objs.map(prop(key));
});
var pluck = compose(map, prop);
//+ (a → b) → [a] → [b]
var map = curry(function map(fn, list){
return reduce(function(acc, x){
acc.push(f(x));
return acc;
}, [], list);
});
//+ a → [a] → [a]
var push = curry(function push(x, xs){
xs.push(x);
return xs;
});
//+ (a → b) → [a] → [b]
var map = curry(function map(fn, list){
return reduce(function(acc, x){
return push(fn(x), acc);
}, [], list);
});
//+ (a → c → d) → (b → c) → a → b → d
var weirdCombinator = curry(function(f, g, a, b){
return f(a, g(b))
});
//+ (a → b) → [a] → [b]
var map = curry(function(f, a){
return reduce(weirdCombinator(flip(push), f), [], a);
});
point-free is cool, but don't let it get out of hand
Given a string of text, find out how many times each letter occurs
This is just an example. It has flaws. Be mad
easy to test
easy to reason about
generalize where it makes sense
break you app into small pieces
use composition to build more complex functions
separate orthogonal concerns
reduce reliance on imperative constructs
work at higher level of abstraction
use JavaScript's simplicity to your advantage
generic data types for generic functions