Skip to content

Instantly share code, notes, and snippets.

@5310
Last active October 9, 2021 13:21
Show Gist options
  • Save 5310/9f277b92b6f4e9b54983 to your computer and use it in GitHub Desktop.
Save 5310/9f277b92b6f4e9b54983 to your computer and use it in GitHub Desktop.
Higher-order functional programming notes with Ramda.js #cheatsheet

This document contains my notes about learning higher-order functional programming with Ramda.js and sometimes Sanctuary and any worthwhile implementations I've written in the process.

Use the Ramtuary REPL for tests.

Warning: Journeyman errors ahead!

// Writing a `pick` funciton that "subsets" objects/maps
// using only functional composition and partial-application.
//
// Refer to https://slack-files.com/T06A4UJJH-F0K2AA5A7-44af64e329 for problem details.
//
// Refer to https://slack-files.com/T06A4UJJH-F0K7D0BFZ-5200d77fbe for derivation.
{ // Pure Ramda.js
const compose = R.compose
const contains = R.contains
const filter = R.pickBy // In rambda, filter is purely by value.
const flip = R.flip
const test = R.pipe(R.equals, console.log)
let ks = ['a', 'c']
let m = {a: 11, b: 22, c: 33}
console.log('Base implementation:')
let pick = compose(filter, compose(flip, flip(contains)))
test(pick(ks)(m), {a: 11, c: 33})
test(pick(ks, m), {a: 11, c: 33}) // => <function> // Unfortunately, JavaScript can't auto-meld.
console.log('Manual flattening:')
let pickB = (ks, m) => pick(ks)(m)
//test(pickB(ks)(m), {a: 11, c: 33}) // ! pick2(...) is not a function; NOT OK // Naturally, it's not auto-curried.
console.log(false) // Dummy failure because I can't be bothered to implement tryCatch and Ramda.tryCatch has only been merged last week.
test(pickB(ks, m), {a: 11, c: 33}) // => {"a": 11, "c": 33}; OK
console.log('Ramda flattening:')
let pickC = R.curryN(2, R.uncurryN(2, pick)) // Ramda uncurry doesn't work as expected.
test(pickC(ks)(m), {a: 11, c: 33}) // => <function>
test(pickC(ks, m), {a: 11, c: 33}) // => <function>
console.log('Currying a manual flattening:')
let pickD = R.curryN(2, (ks, m) => pick(ks)(m)) // Melding by manual uncurry followed by curry.
test(pickD(ks)(m), {a: 11, c: 33})
test(pickD(ks, m), {a: 11, c: 33})
}
{ // Ramda.js + Sanctuary.js
const contains = R.contains
const filter = R.pickBy
const flip = R.flip
const i = R.identity
const meld = S.meld
const pipe = S.pipe
const test = R.pipe(R.equals, console.log)
let ks = ['a', 'c']
let m = {a: 11, b: 22, c: 33}
const pick = meld([pipe([i, flip(contains), flip]), filter])
test(pick(ks)(m), {a: 11, c: 33})
test(pick(ks, m), {a: 11, c: 33})
}
// Functionizing methods by name.
// Much like `R.prop`, but for methods!
// Variadic, and therefore uncurriable.
// (String) -> (*...) -> *
// (m) => (...args, instance) => instance.m(...args)
const method = (m) => (...args) => args.slice(-1)[0][m](args.slice(0, -1))
// Explicitly n-ary, and therefore curriable.
// (Number -> String) -> (*1 -> *2 -> ... -> *n -> *n+1) -> *
// (n, m) => (a1, a2, .., an, instance) => instance.m(a1, a2, .., an)
const methodN = R.curry((n, m) => R.curry(R.nAry(n + 1, (...args) => args[n][m](...args.slice(0, -1)))))
let xs = [10, 20, 30]
console.log(method('join')('_', xs)) // "10_20_30"
console.log(methodN(1, 'join')('-')(xs)) // "10-20-30"
console.log(methodN(1, 'join')('.', xs)) // "10.20.30"
// Flattening higher-order function calls that output functions.
// Flattens a given function that takes all the higher-order arguments together.
// Notice that it's variadic.
const unpartial = (f) => (...args) => {
let g = f(...args.slice(0, f.length))
if (typeof g === 'function') {
return unpartial(g)(...args.slice(f.length))
} else {
return g
}
}
// And the nAry version can also be curried, and return a function.
const unpartialN = (n, f) => R.nAry(n, (...args) => {
let m = n - f.length
let g = f(...args)
if (m == 0) return g
else return unpartialN(m, g)(...args.slice(f.length))
})
// Like R.call, but flattened.
// Likewise variadic.
const callFlat = (f, ...args) => unpartial(f)(...args)
const methodN = R.curry((n, m) => R.curry(R.nAry(n+1, (...args) => args[n][m](args.slice(0, n)))))
let xs = [10, 20, 30]
methodN(1, 'join', '.')(xs) // => <function>, NOT OK
callFlat(methodN, 1, 'join', '-', xs) // => "10-20-30"
unpartial(methodN)(1, 'join', '_', xs) // => "10_20_30"
R.curry(unpartialN(2+1+1, methodN))(1)('join')('.')(xs) // => "10.20.30"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment