I have a function that joins an array of objects with a conditional separator.
function getSegmentsLabel(segments) {
var separator = '-';
var segmentsLabel = '';
var nextSeparator = '';
_.forEach(segments, function(segment) {
segmentsLabel += nextSeparator + segment.label;
nextSeparator = segment.separatorUsed ? separator : ' ';
});
return segmentsLabel;
}
Usages:
var segments = [
{label: 'First', separatorUsed: true},
{label: 'Second', separatorUsed: false},
{label: 'Third', separatorUsed: true},
{label: 'Forth', separatorUsed: true}
];
getSegmentsLabel(segments); // Result: "First-Second Third-Forth"
How can the above getSegmentsLabel
function be written in a purely functional way without mutating variables? We can use lodash functions.
recursion
or instead of map/reduce/join, you can use direct recursion – the benefit here is that we don't iterate thru the collection multiple times to compute the result – oh and the program is really small so it's easy to digest
be careful of stack overflows in javascript tho; relevant: How do I replace while loops with a functional programming alternative without tail call optimization?
var segments = [
{label: 'First', separatorUsed: true},
{label: 'Second', separatorUsed: false},
{label: 'Third', separatorUsed: true},
{label: 'Forth', separatorUsed: true}
];
const main = ([x,...xs]) =>
x === undefined
? ''
: xs.length === 0
? x.label
: x.label + (x.separatorUsed ? '-' : ' ') + main (xs)
console.log (main (segments))
// First-Second Third-Forth
functional programming
that last implementation of our function is awfully specific - functional programming isn't just about using map and reduce, it's about making meaningful abstractions and writing generic procedures that can easily be reused
this example is intentionally very different from your original code with the hope that it will get you to think about programs in a different way – if this stuff interests you, as a follow up to this post, you could start reading about monoids.
by writing our program this way, we've represented this idea of "joinable pieces of text with conditional separators" in a generic text module that could be used in any other program – writers can create units of text using Text.make
and combine them using Text.concat
another advantage in this program is the separator is parameter-controlled
// type Text :: { text :: String, separator :: String }
const Text =
{
// Text.make :: (String × String?) -> Text
make: (text, separator = '') =>
({ type: 'text', text, separator }),
// Text.empty :: Text
empty: () =>
Text.make (''),
// Text.isEmpty :: Text -> Boolean
isEmpty: l =>
l.text === '',
// Text.concat :: (Text × Text) -> Text
concat: (x,y) =>
Text.isEmpty (y)
? x
: Text.make (x.text + x.separator + y.text, y.separator),
// Text.concatAll :: [Text] -> Text
concatAll: ts =>
ts.reduce (Text.concat, Text.empty ())
}
// main :: [Text] -> String
const main = xs =>
Text.concatAll (xs) .text
// data :: [Text]
const data =
[ Text.make ('First', '-'), Text.make ('Second', ' '), Text.make ('Third', '-'), Text.make ('Fourth', '-') ]
console.log (main (data))
// First-Second Third-Fourth
You can use map()
method that will return new array and then join()
to get string form that array.
var segments = [
{label: 'First', separatorUsed: true},
{label: 'Second', separatorUsed: false},
{label: 'Third', separatorUsed: true},
{label: 'Forth', separatorUsed: true}
];
function getSegmentsLabel(segments) {
return segments.map(function(e, i) {
return e.label + (i != segments.length - 1 ? (e.separatorUsed ? '-' : ' ') : '')
}).join('')
}
console.log(getSegmentsLabel(segments));
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With