Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement array joins in functional way?

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.

like image 238
TheKojuEffect Avatar asked Aug 25 '17 20:08

TheKojuEffect


2 Answers

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
like image 51
Mulan Avatar answered Sep 18 '22 08:09

Mulan


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));
like image 37
Nenad Vracar Avatar answered Sep 21 '22 08:09

Nenad Vracar