JS :: Building prompts with Array.reduce and Object.assign

Array.reduce is the swiss army penknife of Javascript.

It simply lets us:

  • start with a seed value, and
  • work through a list item by item – updating the seed value each time.

and it turns out that all manner of things can be done in this way, sparing us from manually setting up loops and iterator values, and protecting us from all the boundary condition accidents and puzzles that loops are heir to.

One use of Array.reduce in Drafts is in creating a prompt from a list of button names.

For the initial seed value, we can use the standard JS Object.assign to return a prompt object with any title etc properties that we need.

For each stage of working through the button list, we define a function with two arguments (accumulator, item), or (a, x).

  • The accumulator is the current state of the changing seed value
  • the item, in this case, is a button name.

Our function just adds a button, and returns the updated prompt.

// A prompt for selecting an item from a list.
const p = categories.reduce(

    // Accumulator updates - a button added for each category in the list:
    (prompt, buttonName) => {
        prompt.addButton(buttonName);
        return prompt;
    },

    // Initial accumulator: a prompt with properties:
    Object.assign(
        Prompt.create(), {
            title: 'Select list:',
            message: 'Ask for a category, and append' +
                ' to a tagged list for that category',
            isCancellable: true
        }
    )
);
4 Likes

This I think requires a big conceptual shift, at least for me, but I think I see the logic. I was completely confused by another code example you posted b/c of the initial const … but presumably this is all creating the object. I’m not sure it’s the easiest code to follow for newcomers but I am intrigued by the approach.

My feeling is that if you are starting out afresh, the mental model provided by

  • map
  • reduce
  • filter

is probably simpler than that of having to set up loops, increment mutable variables, check boundary conditions etc etc.

(It certainly brings down the bug count, by handing the mechanics over to pre-built parts).

If, however, we are already very used to loops and actions, then composing values does involve a bit of a mental shift.

Incidentally, the full context of that snippet might make things clearer – you should find it by expanding the disclosure triangle in this post:

Thanks for posting this. I wanted to see if I could take it further as I’ve experimented with more declarative programming. Here’s a set of Prompt utility functions I’ve defined that I’m using to build prompts in my actions.

// Title, Message -> Prompt
const newPrompt = (t = "", m = "") => 
  Object.assign(Prompt.create(), {title: t, message: m});

// Prompt, Button -> Prompt
const addButton = (p, b) => {p.addButton(b);return p;};

// Title, Buttons -> Prompt
const buttonPrompt = (t, bs) => bs.reduce(addButton, newPrompt(t));

// p = Prompt
// c = ["funcName", [arguments]]
const addControl = (p, c) => {
  const func = c[0], args = c[1];
  p[func](...args);
  return p;
};

// Define a prompt in a object
// {
//   title: "",
//   message: "",
//   controls: [
//     ["funcName", [arguments]],
//     ["funcName", [arguments]]
//   ]
// }
const customPrompt = p => 
  p.controls.reduce(addControl, newPrompt(p.title, p.message));

And here are a few example prompts built with these functions.

const opts = ["Yes", "No", "Maybe"];
const p1 = buttonPrompt("Choose Option", opts);
p1.show();

const p2 = customPrompt({
  title: "Go to Line",
  controls: [
    ["addTextField", ["ln", "Line Number:", "", {"keyboard":"numberPad"}]],
    ["addButton", ["Go"]]
  ]});
p2.show();

const params = ["Param1", "Param2", "Param3"];
const p3 = customPrompt({
  title: "Replace Parameters",
  message: "Fill in all values.",
  controls: params.reduce(
    (c, v, i) => c.concat([["addTextField", [i, v, ""]]]),
    [["addButton", ["Generate Tasks"]]])
  });
p3.show();

I’m not sure that this is necessarily any easier or better, but it was a good exercise to build this and see how to use some of these concepts together.

2 Likes

My experience is that the bug count drops and the code-writing speed picks up when we start to construct values rather than ‘doing’ things, as if we were tiny humunculi running around inside the machine.

The mutate and proceed to next mutation model involves carrying a lot of baggage in the head, and some of it always seems to slip out …

It seems to work better just to define and compose the values we want.

In my short time with JavaScript, I’ve observed the same. I’ve converted a few short scripts from imperative to declarative style and was surprised at how much more concise and easier to read they became.

I wish I spent more time with this back when I was coding for a living and not just a hobby.

1 Like