1

Having this code

const myMagic = (one, two, three, four) => `this is ${one} and ${two} and ${three} and ${four} as usual`

const txt = 'HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&hx'
const fragments = txt.split('&')
const pieces = []

for (let i=0; i<fragments.length-1;i +=5) {
  pieces.push(fragments[i])
  pieces.push(myMagic(fragments[i+1],fragments[i+2],fragments[i+3],fragments[i+4]))
}

pieces.push(fragments[fragments.length-1])

console.log(pieces)

How could I transform it into a more declarative version?

The code is like that since the split is taking a regexp that parses the text only once, and then with these fragments I'm building as many components as needed with myMagic function

So is there any way to write this in a more declarative way without altering the logic?

GWorking
  • 4,011
  • 10
  • 49
  • 90
  • 1
    Define "more declarative"...? You need to loop in groups of five. None of the built-in array functions does that. You could shoehorn this into one of them (maybe), but it would just cost you clarity IMHO. – T.J. Crowder Sep 22 '19 at 15:48
  • @T.J.Crowder Luckily we can write our own combinators in Javascript and adopt the common combinators from FP and then combine/compose them to allow a declarative algorithm also for the given task. –  Sep 22 '19 at 16:48
  • @bob - Yes indeed, FP or otherwise... – T.J. Crowder Sep 22 '19 at 16:51

4 Answers4

2

For me, the sweetspot lies with using some utils you can get from lodash, ramda or any other slightly "functional" library, but keeping the [ a, f(b, c, d, e) ] logic in a regular arrow function. (might be a personal preference)

The steps to take:

  • Split the string in to one array of strings (I use split("&"))
  • Split the array of strings in to an array of arrays of 5 strings (chunk(5))
  • Call flatMap on the outer array
  • Map the inner arrays using ([ head, ...tail]) => [ head, f(...tail) ] where f is your "magic" function

// Utils
const range = s => Array.from(Array(Math.floor(s)), (_, i) => i);
const chunk = n => xs => range(xs.length / n)
  .map(i => xs.slice(i * n, i * n + n));
const split = del => str => str.split(del);
const flatMap = f => xs => xs.flatMap(f);
const pipe = (...fs) => x => fs.reduce((y, f) => f(y), x);

// App
const myMagic = (one, two, three, four) => `this is ${one} and ${two} and ${three} and ${four} as usual`

const convert = pipe(
  split("&"),
  chunk(5),
  flatMap(([ head, ...tail ]) => [ head, myMagic(...tail) ])
);

// Run it
const input = "HELLO1&ho&hy&hu&hq&HELLO2&ho&hy&hu&hq&HELLO3&ho&hy&hu&hq&HELLO4&ho&hy&hu&hq&hx";

console.log(convert(input));
user3297291
  • 22,592
  • 4
  • 29
  • 45
  • nice, but I'd rather call it `chunk`, because ramda's `aperture` is a different thing. – georg Sep 23 '19 at 09:06
  • Ah, I see now. Ramda's aperture makes overlapping groups of size `n`, rather than splitting (which makes sense...). I also just noticed Bergi mentions the name `chunk` as well :D – user3297291 Sep 23 '19 at 09:10
  • I love it when people are using monads in JS! –  Sep 23 '19 at 11:56
  • Thanks a lot for the detailed explanation, I personally still try to avoid using external libraries (something I want to correct anytime soon), but I see what you are doing here so thanks for your time :) Still, comparing the code with Bergi's one, yours is almost twice the length, this is something that definitively disturbs me – GWorking Sep 27 '19 at 07:41
  • You're welcome! For me, the functions in "Utils" are functions I would already have available in my project, from a library like Ramda or self written like here. Therefore, I don't see them as "doubling the length" ;) – user3297291 Sep 27 '19 at 09:28
  • Yes I completely agree, without these functions the code looks indeed more concise ... I definitively need to jump into these libraries, thanks again! – GWorking Sep 28 '19 at 07:12
1

You can always go for a recursive function to traverse lists:

const myMagic = (one, two, three, four) => `this is ${one} and ${two} and ${three} and ${four} as usual`

function pieces([zero, ...rest]) {
    if (!rest.length)
        return [zero];
    const [one, two, three, four, ...more] = rest;
    return [zero, myMagic(one, two, three, four), ...pieces(more)];
}

const txt = 'HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&hx';
console.log(pieces(txt.split('&')))

I'd recommend to use some kind of chunk(5) function though and flatMap over its result.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • Why should I need to flat(), if you're already returning `...pieces(more)`? – GWorking Sep 23 '19 at 08:52
  • @GWorking What `flat()` do you mean? If you're referring to my last paragraph, see user3297291's answer for what I had in mind with `flatMap`. – Bergi Sep 23 '19 at 12:46
  • Yes, I see what you mean, still though I see your option in the answer as the simplest, more concise and intuitive, which at the end is something to be considered. I'm using your solution so thousand thanks! – GWorking Sep 27 '19 at 07:38
1

If you like declarative/functional style, why not to try ramda.js?

let txt = 'HELLO A,1,2,3,4,HELLO B,a,b,c,d,HELLO C,x,y,z,w';
let fragments = txt.split(',');

const myMagic = (one, two, three, four) => `this is ${one} and ${two} and ${three} and ${four} as usual`

//

const convert = R.pipe(
    R.splitEvery(5),
    R.chain(
        R.juxt(R.pair(
            R.head,
            R.pipe(R.tail, R.apply(myMagic))
        ))
    )
)

//


console.log(convert(fragments))
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.min.js"></script>
georg
  • 211,518
  • 52
  • 313
  • 390
  • Thanks! not a user of ramda here, but honestly this code looks ofuscated to me (I know that would change if I got used to it, but still ...) – GWorking Sep 23 '19 at 08:54
  • I'm not a ramda guru either, so I'm sure this can be simplified... Let's ping @ScottSauyet ;) – georg Sep 23 '19 at 09:04
1

Something like this could help if you fancy ramda

const data = 'HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&HELLO&ho&hy&hu&hq&hx'

const toString = ([head, a, b, c, d] = []) => [
  head,
  `this is ${a} and ${b} and ${c} and ${d} as usual`,
]

const magic = R.pipe(
  R.split('&'),
  R.splitEvery(5),
  R.map(toString),
  R.unnest,
  R.init, // to remove last malformed item
);

console.log(
  'result : ',
  magic(data),
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.26.1/ramda.js" integrity="sha256-xB25ljGZ7K2VXnq087unEnoVhvTosWWtqXB4tAtZmHU=" crossorigin="anonymous"></script>
Hitmands
  • 13,491
  • 4
  • 34
  • 69