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?

3
  • 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. Commented Sep 22, 2019 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. Commented Sep 22, 2019 at 16:48
  • @bob - Yes indeed, FP or otherwise... Commented Sep 22, 2019 at 16:51

4 Answers 4

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));

Sign up to request clarification or add additional context in comments.

6 Comments

nice, but I'd rather call it chunk, because ramda's aperture is a different thing.
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
I love it when people are using monads in JS!
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
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" ;)
|
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.

3 Comments

Why should I need to flat(), if you're already returning ...pieces(more)?
@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.
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!
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>

2 Comments

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 ...)
I'm not a ramda guru either, so I'm sure this can be simplified... Let's ping @ScottSauyet ;)
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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.