-1

Basically, my problem comes down to composing the functionality of several functions of this signature:

(a: A, b: B) => c: C

To simplify, I need to do something like this, where each of a, b, and c are different types:

const upper = (a, b) => {
  const c = a.toString().toUpperCase();
  return c;
}

const pad = (a, b) => {
  const c = a.toString().padStart(2, '0');
  return c;
}

const repeat = (a, b) => {
  const c = a.toString().repeat(2);
  return c;
}

const format = compose(repeat, pad, upper);

For the record b is used, I'm just omitting it here for simplicity, but assume it must be provided to each function, and each will receive the same value as an argument.

So far, I've been able to solve for the issue of (a, b) => c not being composable due to the number of arguments by currying the functions, flipping the params, and partially applying b to each before I compose them:

I'm using standard compose and flip utilities like so: https://medium.com/javascript-scene/curry-and-function-composition-2c208d774983

const compose = (...fns) => x => fns.reduceRight((y, f) => f(y), x);

const flip = fn => a => b => fn(b)(a);

const fnArr = [
  repeat,
  pad,
  upper
];

const makeFormatter = functionArray => (a, b) => {
  const flippedAndPartiallyApplied = functionArray.map(fn =>
    flip(curry(fn))(b)
  );
  return compose(...flippedAndPartiallyApplied)(a);
};

const formatter = makeFormatter(fnArr);

So with this, I've gotten the whole b issue out of the way, and I can compose several unary functions. But my issue remains that each of my functions are a => c.

Notice how each of my functions must call .toString(). I need this step before I can modify and return my value. Unfortunately, I need to keep true to each function keeping that same signature.

Is there any way I can further modify each function programatically in a way that I don't need to modify the function source codes, but can mutate them in a way that fits my composition pipeline, or is this just not possible?


I'm working with a API that runs calendar days through a function you provide to the API that can be used to render a day cell to a datepicker UI. This is the API for reference, and this is an example.

I have a use case where I could have multiple renderDay functions provided through various sources in my app. I'd like to combine these functions somehow because the API only accepts one, and I'd like to do it in an elegant way.

Esten
  • 1,385
  • 2
  • 12
  • 21
  • "_...I can compose several unary functions. But my issue remains that each of my functions are `a => c`_" I don't think this is correct. Your example uses `String -> String` functions, which are composable. But you mention earlier that "_where each of a, b, and c are different types_". You cannot compose functions of `a -> c` without knowing how to go from `c -> a` or `[c] -> c` I'm afraid. – user3297291 Mar 26 '21 at 08:18
  • @user3297291 I should have been more explicit about the types. See how I'm calling `toString` within each function? It's because the final, composed function must produce a string (in actuality it's a React Element), but the `a` argument is of a different type. In my example above, you could assume it's a number (in actuality its a js Date object). The type conversion will always be the same in each function: `number -> string` in the above code, or `Date -> React Element` in actuality. So I know I need to call `toString`/`getDate` in every function on the `a` param. – Esten Mar 26 '21 at 13:52
  • Function composition for 3 functions can be written as `( (c -> d), (b -> c), (a -> b) ) -> (a -> d)` If we forget about the second argument, what you’re trying to do is: `( (number -> string), (number -> string), (number -> string) ) -> (number -> string) )` These functions can not be composed using your reduce based utility because the output type of the first function does not match the input of the second, etc. – user3297291 Mar 26 '21 at 14:03

2 Answers2

2

If you have a list of (a, b) -> c and need to bring that back to just one function of (a, b) -> c, I think you can rephrase the problem to defining [c] -> c.

For example:

const formatter1 = (a, b) => a.toUpperCase();
const formatter2 = (a, b) => `${a}-${a}`;
const formatters = [formatter1, formatter2];

// Generic combiner of formatters
const applyAll = fs => (a, b) => fs.map(f => f(a, b));

// Reducer logic
const takeFirst = cs => cs[0];
const concat = cs => cs.reduce(
  (acc, c) => acc + c + "\n", "");
const takeLongest = cs => cs.reduce(
  (acc, c) => acc.length > c.length ? acc : c, "" );
  
// Construct a formatter by composing applyAll and a reducer
const compose = (f, g) => (...args) => f(g(...args));

const formatterA = compose(takeFirst, applyAll(formatters));
const formatterB = compose(concat, applyAll(formatters));
const formatterC = compose(takeLongest, applyAll(formatters));

console.log("Take first:\n", formatterA("a", "b"));
console.log("Concat:\n", formatterB("a", "b"));
console.log("Take longest:\n", formatterC("a", "b"));

What you essentially have to define is how to combine the Elements that are produced by the different formatters.

There is not one right answer to this question. The most straight forward answer would be to concatenate the elements:

const dayFormatter = (date, opts) => (
  <>
     {allFormatters.map(f => f(date, opts))}
  </>
)
user3297291
  • 22,592
  • 4
  • 29
  • 45
  • Thank you for the response! I'm trying to digest it and apply to my example. I just edited my question, and I'm hoping it's more clear now. If you get a chance, let me know if this changes your answer. Thank you – Esten Mar 25 '21 at 21:34
1

How about equipping the different renderDay-functions with an additional predicate that tells whether it isApplicable to a given day or not, and then going through the list of such DayRenderers, and selecting the first that is applicable?

type DayRenderer = (d: Date, mod: DayModifiers) => React.ReactNode

type DayRendererSelector = {
  isApplicable: (d: Date, modifiers: DayModifiers) => boolean,
  renderDay: DayRenderer
};

function combineDayRenderers(
  cases: DayRendererSelector[],
  defaultRenderer: DayRenderer
): DayRenderer {
  return (d: Date, mod: DayModifiers) => {
    for (const { isApplicable, renderDay } of cases) {
      if (isApplicable(d, mod)) {
        return renderDay(d, mod);
      }
    }
    return defaultRenderer(d, mod);
  }
}

If I had to name that "pattern", it would be "vertical composition of partial functions" (something like what's being discussed here). It's basically the same as a switch { case ... case ... case ... default: ... }, but with a list of cases.

Andrey Tyukin
  • 43,673
  • 4
  • 57
  • 93
  • This is exactly how I would do it. – Linda Paiste Mar 19 '21 at 23:16
  • Thank you for the response, but I don't think it quite applies. I think my question was both too specific, and also too vague. I've updated it to provide some generic code to help explain my issue. – Esten Mar 25 '21 at 21:36
  • @Esten It seems that your entire question now consists entirely of Y from the X-Y-problem, completely omitting the description of the actual problem. Can you please give a plain, simple example of two functions of type `(a, b) => c`, the input, and the desired output? Of course, feel free to show your solution attempt also, but only after you've described precisely what the problem is. Also, sorry, but I don't understand your attempts to abstract the original problem. I also think that it's not the most appropriate circumstances for over-the-top abstractions: I mean, it's a calendar UI...? – Andrey Tyukin Mar 25 '21 at 21:46