Programming With Functions – Common Examples – A Quick Detour



Programming With Functions – Common Examples – A Quick Detour

0 1


programming-with-functions

Slide from a talk I gave

On Github wilhelmson / programming-with-functions

Programming With Functions

@jessewilliamson

wilhelmson.github.io

Functions in Math

x → y

[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

square(3) = 9

always

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

Functions As Behaviors

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

Function Composition

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

Common Examples

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

Map

[a] → (a → b) → [b]

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

Folds are amazing

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

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

Rethinking map

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

Putting It All Together

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

Final Thoughts

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

References

Reading:
Watching:
Courses:

Thanks