Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@suissa
Forked from jbmusso/fold.js
Last active July 11, 2017 19:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save suissa/171c7c7bb1f5692a4036a68fffea9256 to your computer and use it in GitHub Desktop.
Save suissa/171c7c7bb1f5692a4036a68fffea9256 to your computer and use it in GitHub Desktop.
Functional acrobatics using folds in JavaScript.
/*eslint-env es6 */
// Inspired by the paper "A tutorial on the universality and
// expressiveness of fold" by Graham Hutton ( available at
// http://www.cs.nott.ac.uk/~gmh/fold.pdf ), implementing some generic
// list handling functions in JavaScript in terms of `fold`.
// Personally I had an enlightnening moment when I realised the
// beautiful interplay of cons lists and foldr during the FP101x
// Haskell course. JavaScript's syntax doesn't make this very apparent
// at first, but it's still there if you analyze the `map` and
// `filter` implementations below.
// Essential constructs
const cons = ( x, xs ) => [ x ].concat( xs )
const car = xs => xs[ 0 ]
const cdr = xs => xs.slice( 1 )
const foldr = ( f, v, xs ) => xs.reduceRight( f, v )
const foldl = ( f, v, xs ) => xs.reduce( f, v )
const partial = ( f, ...xs ) => f.bind.apply( f, [ null, ...xs ] )
const id = x => x
// Helpers
const add = ( x, y ) => x + y
const even = x => x % 2 === 0
const and = ( x, y ) => x && y
const or = ( x, y ) => x || y
const times = ( x, y ) => x * y
const double = x => 2 * x
const isEmpty = xs => xs.length === 0
const range = n => Array.from( { length: n }, ( v, i ) => i )
// Folds, point-free style!
const sum = partial( foldr, add, 0 )
const all = partial( foldr, and, true )
const any = partial( foldr, or, false )
const product = partial( foldr, times, 1 )
// Folding like a boss!
const length = xs => foldr( ( acc, _ ) => 1 + acc , 0, xs )
const reverse = xs => foldl( ( acc, curr ) => cons( curr, acc ), [], xs )
const map = ( f, xs ) => foldr( ( acc, curr ) => cons( f( curr ), acc ), [], xs )
const filter = ( f, xs ) => foldr( ( acc, curr ) => f( curr ) ? cons( curr, acc ) : acc, [], xs )
// Ok, let's test these in practice.
console.log( 'cons( 0, [ 1, 2, 3 ] ) =', cons( 0, [ 1, 2, 3 ] ) )
// => cons( 0, [ 1, 2, 3 ] ) = [ 0, 1, 2, 3 ]
console.log( 'car( [ 1, 2, 3 ] ) =', car( [ 1, 2, 3 ] ) )
// => car( [ 1, 2, 3 ] ) = 1
console.log( 'cdr( [ 1, 2, 3 ] ) =', cdr( [ 1, 2, 3 ] ) )
// => cdr( [ 1, 2, 3 ] ) = [ 2, 3 ]
console.log( 'car( cdr( [ 1, 2, 3 ] ) ) =', car( cdr( [ 1, 2, 3 ] ) ) )
// => car( cdr( [ 1, 2, 3 ] ) ) = 2
console.log( 'range( 10 ) =', range( 10 ) )
// => range( 10 ) = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
console.log( 'sum( range( 10 ) ) =', sum( range( 10 ) ) )
// => sum( range( 10 ) ) = 45
console.log( 'all( [ true, true, true ] ) =', all( [ true, true, true ] ) )
// => all( [ true, true, true ] ) = true
console.log( 'all( [ true, true, false ] ) =', all( [ true, true, false ] ) )
// => all( [ true, true, false ] ) = false
console.log( 'any( [ true, true, true ] ) =', any( [ true, true, true ] ) )
// => any( [ true, true, true ] ) = true
console.log( 'any( [ false, false, true ] ) =', any( [ false, false, true ] ) )
// => any( [ false, false, true ] ) = true
console.log( 'length( range( 0 ) ) =', length( range( 0 ) ) )
// => length( range( 0 ) ) = 0
console.log( 'length( range( 16 ) ) =', length( range( 16 ) ) )
// => length( range( 16 ) ) = 16
console.log( 'reverse( range( 10 ) ) =', reverse( range( 10 ) ) )
// => reverse( range( 10 ) ) = [ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]
console.log( 'car( cdr( reverse( range( 10 ) ) ) ) =', car( cdr( reverse( range( 10 ) ) ) ) )
// => car( cdr( reverse( range( 10 ) ) ) ) = 8
console.log( 'cons( 10, reverse( range( 10 ) ) ) =', cons( 10, reverse( range( 10 ) ) ) )
// => cons( 10, reverse( range( 10 ) ) ) = [ 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 ]
console.log( 'map( double, range( 5 ) ) =', map( double, range( 5 ) ) )
// => map( double, range( 5 ) ) = [ 0, 2, 4, 6, 8 ]
console.log( 'filter( even, range( 10 ) ) =', filter( even, range( 10 ) ) )
// => filter( even, range( 10 ) ) = [ 0, 2, 4, 6, 8 ]
console.log( 'map( double, filter( even, reverse( range( 10 ) ) ) ) =', map( double, filter( even, reverse( range( 10 ) ) ) ) )
// => map( double, filter( even, reverse( range( 10 ) ) ) ) = [ 16, 12, 8, 4, 0 ]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment