Ramda compose throws errors with filter/map

59 views Asked by At

I'm learning ramda by rewriting a function we have that parses numbers inside parenthesis in an array of strings. When I write this as a single compose function, both commented filter and map lines throw an error:

Cannot read properties of undefined (reading 'fantasy-land/filter')

Yet when I write them as two different compose functions, they work fine.

The match/nth compose returns [ '123', '456', '789' ]

When I debug, it looks like R.filter and R.map are operating on the individual characters of the strings instead of each element that R.nth is returning.

[ [ 1, 2, 3 ], [ 4, 5, 6 ], [ 7, 8, 9 ] ]

Not working:

const extractNumbersInParenthesesFilterAndConvert = R.compose(
  R.map(Number), // errors
  R.filter(Boolean), // errors
  R.nth(1),
  R.match(/\((\d+)\)$/)
);

// Function to process an array of strings and return the extracted numbers
const extractNumbersFromStrings = R.map(extractNumbersInParenthesesFilterAndConvert);

const inputArray = [
  'Some text (123)',
  'Another string (456)',
  'No numbers here',
  'Last string (789)',
];

let result = extractNumbersFromStrings(inputArray);

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.29.0/ramda.js"></script>

Working:

const extractNumbersInParentheses = R.compose(
  // R.map(Number), // errors
  // R.filter(Boolean), // errors
  R.nth(1),
  R.match(/\((\d+)\)$/)
);

const filterAndConvert = R.compose(
  R.map(Number),
  R.filter(Boolean)
);

// Function to process an array of strings and return the extracted numbers
const extractNumbersFromStrings = R.map(extractNumbersInParentheses);

const inputArray = [
  'Some text (123)',
  'Another string (456)',
  'No numbers here',
  'Last string (789)',
];

let result = extractNumbersFromStrings(inputArray);
result = filterAndConvert(result)

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.29.0/ramda.js"></script>

2

There are 2 answers

0
helion3 On

The issue is that the R.match/R.nth functions are being run via R.map on each string in the array, but R.filter/R.map are meant to run on the results of that map, yet are inside the compose so they're running on the result of each individual string regex/nth.

Throwing an R.tap in there to debug confirms:

const full = R.compose(
  R.tap(x => console.log(typeof x, x)),
  R.nth(1),
  R.match(/\((\d+)\)$/)
);
string 123
string 456
undefined undefined
string 789

It works as two composes because one operates on each string while the second operates on the results. Knowing this, it can be rewritten a variety of ways. Here's one:

const extractCounts= R.compose(R.nth(1), R.match(/\((\d+)\)$/))
const filterAndConvert = R.compose(R.map(Number), R.filter(Boolean))
const parseNumbersFromParenthesis = R.compose(filterAndConvert, R.map(extractCounts))

console.log(parseNumbersFromParenthesis([
  'Some text (123)',
  'Another string (456)',
  'No numbers here',
  'Last string (789)',
])) // prints [ 123, 456, 789 ]
0
Ori Drori On

You need one function to convert a string to a number, and another to map the array, convert the strings to numbers, and then filter out NaN values.

Don't use Boolean because they would also remove 0 values. Use Math.isNaN() instead.

const { compose, match, last, map, reject } = R;

const extractNumbersInParentheses = compose(
  Number, // convert to number
  last, // take the last match (the capture group)
  match(/\((\d+)\)$/) // extract number
);

// Function to process an array of strings and return the extracted numbers
const extractNumbersFromStrings = compose(
  reject(Number.isNaN), // remove NaN items
  map(extractNumbersInParentheses)
);

const inputArray = ['Some text (123)', 'Another string (456)',  'No numbers here', 'Last string (789)'];

const result = extractNumbersFromStrings(inputArray);

console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.29.0/ramda.js"></script>