Pre-requisite ramda posts:
Other ramda posts:
This is the test data set we will reference throughout the post:
const films = [
{ title: 'The Empire Strikes Back', rating: 8.8 },
{ title: 'Pulp Fiction', rating: 8.9 },
{ title: 'The Deer Hunter', rating: 8.2 },
{ title: 'The Lion King', rating: 8.5 }
]
There are a few conditions that are required for us to meet our goal. We must construct a function that: * only selects those with an 8.8 rating or higher * returns a list of the selected titles interpolated in an HTML string that has this structure: html <div>TITLE: <strong>SCORE</strong></div>
Given these requirements, a pseudotype signature for this might be:
// `output` takes in a list of films
// and returns a list of HTML strings
//
// output :: [Film] -> [Html]
films.map(film => `<div>${film.title}, <strong>${film.rating}</strong></div>`)
// => [
// "<div>The Empire Strikes Back, <strong>8.8</strong></div>",
// "<div>Pulp Fiction, <strong>8.9</strong></div>",
// "<div>The Deer Hunter, <strong>8.2</strong></div>",
// "<div>The Lion King, <strong>8.5</strong></div>"
// ]
Try this code in the ramda REPL
map
Callback// filmHtml :: Film -> Html
const filmHtml = film =>
`<div>${film.title}, <strong>${film.rating}</strong></div>`
films.map(filmHtml)
Try this code in the ramda REPL
filter
Out Lower Scoresfilms
.filter(x => x.rating >= 8.8)
.map(filmHtml)
// => [
// "<div>The Empire Strikes Back, <strong>8.8</strong></div>",
// "<div>Pulp Fiction, <strong>8.9</strong></div>",
// ]
Try this code in the ramda REPL
But wait! We can extract that filter
callback, as well:
// hasHighScore :: Film -> Bool
const hasHighScore = x =>
x.rating >= 8.8
films
.filter(hasHighScore)
.map(filmHtml)
Try this code in the ramda REPL
filter
and map
We can use ramda’s function currying capabilities and function composition to create some very clear and concise pointfree functions.
import { compose, filter, map } from 'ramda'
// output :: [Film] -> [Html]
const output =
compose(map(filmHtml), filter(hasHighScore))
output(films)
Try this code in the ramda REPL
One thing to remember with ramda functions (like map
and filter
) is that ramda typically orders arguments from least likely to change to most likely to change. Callback/transformation functions here are passed as the first argument, and the data comes last. To understand this further, check out the following links:
If we want to not only reuse our filtering and mapping functions but also make them more readable, we can pull out the pieces that make up our output
function into smaller bits:
// filmsToHtml :: [Film] -> [Html]
const filmsToHtml =
map(filmHtml)
// highScores :: [Film] -> [Film]
const highScores =
filter(hasHighScore)
// output :: [Film] -> [Html]
const output =
compose(filmsToHtml, highScores)
output(films)
Try this code in the ramda REPL
reduce
We can accomplish the same goals as filter
and map
by making use of reduce
.
films.reduce((acc, x) => {
return hasHighScore(x)
? acc.concat(filmHtml(x))
: acc
}, [])
// or, for better performance
films.reduce((acc, x) => {
if (hasHighScore(x)) {
acc.push(filmHtml(x))
}
return acc
}, [])
Try this code in the ramda REPL
If you’re not familiar with reduce, be sure to play with the live example to better understand how those pieces work before moving on.
It’s also worth noting that you can do just about anything in JavaScript with the reduce
function. I highly recommend going through Kyle Hill’s slides on reduce Is The Omnifunction.
But wait! We can extract the reduce
callback like we did with map
and filter
before:
// highScoresHtml :: ([Html], Film) -> [Html]
const highScoresHtml = (acc, x) =>
hasHighScore(x)
? acc.concat(filmHtml(x))
: acc
films.reduce(highScoresHtml, [])
Try this code in the ramda REPL
import { reduce } from 'ramda'
const output =
reduce(highScoresHtml, [])
output(films)
Try this code in the ramda REPL
As before with map
& filter
, output
can be reused over and over again and passed any set of films to generate HTML for. To further understand the parameter order used here, check out the docs for ramda’s reduce
.
This step-by-step process we’ve walked through is as close to real-life refactoring/rethinking as I could do in a post. Thanks for making it this far.
Until next time,
Robert