Programming With Functions
@jessewilliamson
wilhelmson.github.io
[A] function is a relation between a set of inputs and a set of permissible outputs with the property that each input is related to exactly one output.
-Wikipediaso you know it's legit
The set of inputs is the domain
The set of outputs is the codomain
- In programming, this represents the type of a function
Functions map an argument to a value
x → y
each argument maps to exactly one value
Functions in Programming
? → ?
Functions do all kinds of things
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"
- these aren't true in all languages, but most
- functions in programming can be more like functions in math, but usually aren't
Functions in JavaScript
are awesome and weird
functions are first-class members
functions are objects
this
- as first class members they are the same as any value. the can be stored in variables, passed to and returned from functions
- as objects they have useful, and not so useful properties. Function.prototype.length, apply, call, name
- this if you don't think a function having this is weird, I don't know what to say
- don't use this in a function if you can avoid it
Our functions tend to be...
highly specific
highly imperative
not really like functions at all
- this is true in most langs
- we like our OO code, so our functions are usually specific to objects
- highly concerned with the steps involved. think of a for loop
- they usually hang off objects (methods)
We should use more functions
like, lots more
It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures.
-Obligatory Alan Perlis Quote
- this quote is called out in lots of FP talks
- The idea is to have simple functions that operate on simple data structures
- The more specific a function is, the less reusable it is
Functional thinking
- it's not just about new techniques, it's about changing how you approach problems
Solve problems with verbs
- we tend to think of programs in nouns, and verbs are attached to them
- this is your normal OO way of doing things
- verbs without nouns are more freely reusable
- doesn't require us to make up new nouns just to carry around our verbs, *cough* Java *cough*
Think about results not steps
- fp allows you to work at a higher level of abstraction
- you're not concerned with things like iteration
Think in terms of transformations
- this style of programming is especially useful for transforming data
- break problems into a series of transformations
Keep it simple
use simple data structures
use simple functions
- simple structures are easy to transform
- specifc structures reduce reusability
- the same is true for simple, generic functions
- IMO I get more reuse from functions that "classes"
Pure Functions
x → y
- so what types of functions are we talking about
A pure function...
takes in arguments and returns values
uses no hidden state
has no side effects
is referentially transparent
- pure functions are like functions in mathematics
- relational equality/referential transparency is to replace function call with its return value without changing the program
- purity is hard in JS. It's not built-in to the data types. However, you can bring in things like Mori
- I'm not going to enforce 100% purity in the examples
More Importantly
they are easy to reason about, and test
but most importantly pure functions are...
Composable
- composition is key to programming in this style
- it's what makes this all work
Even the simplest behavior can become a function
Constructs we take for granted can be abstracted into functions
if, not, and, or, for loops, existence, indexability
- we create abstractions when we see duplication or a chance for reuse
- why not simple things too, why should you ever write a for loop again?
- the idea is to string things together more declaratively
prop
function prop(name,obj){
return obj[name];
}
- This function may not seem very useful, but with a few changes it can be pretty powerful
not
function not(val){
return !val;
}
Why would I even?
because little, general, pure functions are
Composable
- there's that word again
- again, the goal is to write programs more declartivly
- to concern ourselve with the what, not the how
- abstracting behaviors helps to do that
f = x → y
g = y → z
h = g ∘ f == x → z
(b → c) → (a → b) → a → c
evil Haskell type signature
- these signatures will pop up in different places in the examples.
- mostly when it seems to clarify things
It's like chaining, only in reverse
Chaining in Underscore/Lo-Dash
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
Now using composition
and maybe a couple other tricks, but nevermind that right now
var stooges = [{name: 'curly', age: 25}, {name: 'moe', age: 21}, {name: 'larry', age: 23}];
compose(first, sortBy(prop("age")))(stooges); //=> { age: 21, name: "moe" }
- chaining is cool, but it's a one time thing
- you can't chain some functions together and save that in a variable
- compose returns a function, so we can hold on to that or do anything we want with it
Note that the calls go from right to left
Each function feeds its result to the next
compose(h, g, f)(a) === h(g(f(a)))
Composition gives us a way to make new functions from smaller ones
high-fives all around
- this is were having small behaviors comes in handy
- so what, do you just write all your functions with one arg?
So just write all your functions with one argument
thank you and good night
Currying
- named after the man who discovered the idea Haskell Curry
- but he wasn't the only one - Schonfinkel
[C]urrying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument
-Wikipedia (again)but still totally legit
function add(a){
return function(b){
return a + b;
};
}
add(1)(2) //=> 3 (pause for effect)
Currying gives us a another way to make new functions from old ones
we delay the application of some arguments
var add10 = add(10);
[1, 2, 3].map(add10); //=> [11, 12, 13];
Stooges revisited
var stooges = [{name: 'curly', age: 25}, {name: 'moe', age: 21}, {name: 'larry', age: 23}];
compose(first, sortBy(prop("age")))(stooges); //=> { age: 21, name: "moe" }
- The previous example works because prop was curried
- it was waiting for an object to get data from
- it got those object from sortBy, one by one
Writing manually curried functions is tedious
which is why cool languages like Haskell curry every function
- other languages have functions like partial built in
There's got to be a better way
there is, but more on that later
Higher Order Functions
functions in and/or functions out
If you've every passed a callback, you've used higher order functions
map
[a] → (a → b) → [b]
[1, 2, 3].map(
function(n){
return n * 2;
}); //=> [2, 4, 6]
filter
[a] → (a → bool) → [a]
[1, 2, 3].filter(
function(n){
return n % 2 == 0;
}); //=> [2]
reduce
aka fold
[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
These functions share a common problem
Filter
[a] → (a → bool) → [a]
Reduce
[a] → (b → a → b) → b → b
The data always comes first
which means they're not composable
- also they're not curried, but that can be changed
Put your data last
- think of data not as an argument, but as your functions inputs
- put your inputs last
- put the stuff you want to reuse first
- this enables point-free programming (creating functions without arguments)
A Quick Detour
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
Some of your favorite functions are folds
map, filter, forEach
What else can we do with a fold
sum
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);
allTrue & anyTrue
var allTrue = reduce(and)(true)
function and(a, b){
return a && b;
}
var anyTrue = reduce(or)(false)
function or(a, b){
return a || b;
}
- using folds greatly simplifies how you solve a problem
- you don't need to think about looping, etc, you just think about the values you have and how you combine them
Combinators
functions in and functions out
Higher-order pure functions that take only functions as arguments and return a function.
-Reg Braithwaite JavaScript Allonge
combinators take functions and combine them into new ones
compose
//+ (b → c) → (a → b) → a → c
function compose(g, f){
return function(a){
return g(f(a));
};
}
- this is less useful that compose in most libraries because it is binary
A Traditional Combinator
flip
Flip
//+ (a → b → c) → b → a → c
function flip(fn){
return function(b){
return function(a){
return fn(a, b);
}
}
}
A Better map
//+ (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]
- since flip is curried, betterMap is curried and we can use it to make composable mapping functions
Safe integer parsing
map(parseInt, ['1', '2', '3']); //=>[1, NaN, NaN]
- parseInt takes a string and a radix, map sends the current value and index to whatever function you suppy, so parseInt dies
Radix season
var safeParseInt = flip(parseInt)(10);
map(safeParseInt, ['1', '2', '3']); //=> [1, 2, 3];
- by flipping parseInt and specifying 10 as the radix, we have a unary parseInt function for base 10
An Argument For Combinators
Logging
Super A++ #1 Enterprise App
function superAwesomeLogger(){
console.log([].slice.call(arguments));
}
function add(a,b){
return a + b;
}
before
//+ (a → b) → (a → c) → a → c
function before(decorator){
return function(target){
return function(){
decorator.apply(this, arguments);
return target.apply(this, arguments);
}
}
}
after
//+ (a → b) → (a → c) → a → c
function after(decorator){
return function(target){
return function(){
var val = target.apply(this, arguments);
decorator(val);
return val;
}
}
}
LOG ALL THE THINGS
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.
decorators take a function and return a new one that is a variation of the original.
Partial Application With Decorators
curry
More than one flavor of curry
Explicit arity
function curry2(f){
return function(a){
return function(b){
return f(a, b);
}
}
}
- the benefit here is the explicitness. the downside is calling function one arg at a time, and having to right curry[n] functions
- this could be rewritten so that multiple args can be applied at once
Variadic currry
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));
}
}
}
- my preferred curry function. allows you to curry a function of any length
- downside is explicitness, and it sends more args to the function that it might want
- you don't have to right this, lo-dash has it built in and better.
- you can make curry[n] with this
Making curry[n] with curry
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
Abstracting Common Constructs
guarding against null & undefined
maybe
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
- even maybe is too specific; it's really just a specific case of conditional execution
Generalized Guards
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;
});
Separate concerns. Use combinators and decorators to bring them together
- with curry, the secondary concern is applying arguments in succession
- before/after allow you to specify what the concern is and when to run it
- with provided, the concern is when to run your function
- other examples include once, and fluent
- you don't have to bake these things into your functions
Functions All The Way Down
Revisiting maybe
taken from JavaScript Combinators by Reg Braithwaite
var maybe = curry(function(fn, val){
return val != null ? fn(val) : void 0;
});
A specialized guard
var maybe = provided(function(val){
return val != null;
});
Generalizing further
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);
- we could have written a function called exists
- and then skipped the except, the idea of negating a predicate is interesting
- even if it might be overkill
The Most Common Use For map
pluck
taken from JavaScript Combinators by Reg Braithwaite
var pluck = curry(function(key, objs){
return objs.map(function(obj){
return obj[key];
});
});
Generalizing with prop
var pluck = curry(function(key, objs){
return objs.map(prop(key));
});
Simplifying with compose
var pluck = compose(map, prop);
- we'll call pluck with a name, and the resulting, partially applied function gets fed into map
- map now has a function waiting for an object, and map is waiting for an array
map is just a specialized reduce
//+ (a → b) → [a] → [b]
var map = curry(function map(fn, list){
return reduce(function(acc, x){
acc.push(f(x));
return acc;
}, [], list);
});
So what can be made generic
push
//+ 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);
});
What if we think more abstractly
- push is specific to this case
- abstractly, it's just a binary function, that needs some other function applied to it's second argument
Getting weird
//+ (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);
});
Use common sense
point-free is cool, but don't let it get out of hand
Letter Counts
Given a string of text, find out how many times each letter occurs
Disclaimer:
This is just an example. It has flaws. Be mad
Favor Simple Pure Functions
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
Use Higher Order Functions
separate orthogonal concerns
reduce reliance on imperative constructs
work at higher level of abstraction
Favor Simple Data Types
use JavaScript's simplicity to your advantage
generic data types for generic functions
Learn You A New Language
better yet...
Learn You A New Paradigm
- learning a new lang is one thing, a new paradigm requires learning whole new ways to approach problems
- luckily some langs require you to learn a new paradigm
- or just challenge yourself. give yourself constraints. different.js