functional 101
The idiomatic JavaScript solution is to map-join. But Functional programming is about breaking the program into reusable modules and creating barriers of abstraction -
// Task.js
const empty =
{ id: 0, task: "" }
const task = (id = 0, task = "") =>
({ id, task })
const toString = (t = empty) =>
`${t.id}. ${t.task}`
const toStringAll = ([ first, ...rest ]) =>
rest.reduce // <-- reduce
( (r, x) => r + ", " + toString(x)
, toString(first)
)
export { empty, task, toString, toStringAll }
So there's a possible and sensible implementation using reduce. Readability of this program is good because each part of the module is small and does just one thing.
Now we close the imaginary lid on our module and forget all of the complexity within. What remains is a clean interface that clearly communicates the capabilities of the module -
// Main.js
import { task, toStringAll } from './Task'
const data =
[ task(7, "foo")
, task(22, "bar")
, task(33, "qux")
]
console.log(toStringAll(data))
// 7. foo, 22. bar, 33. qux
console.log(toStringAll(data.slice(0,2)))
// 7. foo, 22. bar
console.log(toStringAll(data.slice(0,1)))
// 7. foo
console.log(toStringAll(data.slice(0,0)))
// 0.
Expand the snippet below to verify the result in your browser -
const empty =
{ id: 0, task: "" }
const task = (id = 0, task = "") =>
({ id, task })
const toString = (t = empty) =>
`${t.id}. ${t.task}`
const toStringAll = ([ first, ...rest ]) =>
rest.reduce
( (r, x) => r + ", " + toString(x)
, toString(first)
)
const data =
[ task(7, "foo")
, task(22, "bar")
, task(33, "qux")
]
console.log(toStringAll(data))
// 7. foo, 22. bar, 33. qux
console.log(toStringAll(data.slice(0,2)))
// 7. foo, 22. bar
console.log(toStringAll(data.slice(0,1)))
// 7. foo
console.log(toStringAll(data.slice(0,0)))
// 0.
the barrier of abstraction
Main.js is separated from Task by a barrier of abstraction. Now we can choose any implementation for toString and toStringAll without requiring Main to concern itself with how Task operates under the lid.
Let's practice making a change and update how an empty task is represented. In the program above we see 0. but we will make it say Empty. instead. And just for fun, let's try a new implementation of toStringAll -
// Task.js
const empty = //
const task = //
const toString = (t = empty) =>
t === empty
? `Empty.` // <-- custom representation for empty
: `${t.id}. ${t.task}`
const toStringAll = ([ t = empty, ...more ]) =>
more.length === 0
? toString(t)
: toString(t) + ", " + toStringAll(more) // <-- recursive
//
export { empty, task, toString, toStringAll }
Main doesn't need to do anything differently -
// Main.js
import { task, toStringAll } from './Task'
const data = //
console.log(toStringAll(data))
// 7. foo, 22. bar, 33. qux
console.log(toStringAll(data.slice(0,2)))
// 7. foo, 22. bar
console.log(toStringAll(data.slice(0,1)))
// 7. foo
console.log(toStringAll(data.slice(0,0)))
// Empty.
Expand the snippet below to verify the result in your browser -
const empty =
{ id: 0, task: "" }
const task = (id = 0, task = "") =>
({ id, task })
const toString = (t = empty) =>
t === empty
? `Empty.`
: `${t.id}. ${t.task}`
const toStringAll = ([ t = empty, ...more ]) =>
more.length === 0
? toString(t)
: toString(t) + ", " + toStringAll(more)
const data =
[ task(7, "foo")
, task(22, "bar")
, task(33, "qux")
]
console.log(toStringAll(data))
// 7. foo, 22. bar, 33. qux
console.log(toStringAll(data.slice(0,2)))
// 7. foo, 22. bar
console.log(toStringAll(data.slice(0,1)))
// 7. foo
console.log(toStringAll(data.slice(0,0)))
// Empty.