Hey all,
I want to be able to be able to do FP like operations using VPL primitives vs having to drop into JS.
Example.
Suppose I wanted to implement a visual version of reduce (or "fold"). Suppose I wanted to multiply the accumulator by each value in an array on each reduce iteration.
In a raw function
node
, I can do this quite easily:
const { init, values } = msg.payload
return values.reduce((acc, it) => acc * it) , init)
Then if I sent a message of { init: 1, values: [2,2,2,3] }
, the node
would emit 24
.
Neat. But what if I'm map/filter/reduce/flattening over a series of functions? It'd be nice to get that visualization surfaced into red, vs being buried in javascript.
Here's an implementation of a reduce
function wired into a multiplication
function (*
):
The idea here is that reduce
could be implemented in such a way where iteration values could be piped thru user processing flows--such as *
--then on completion, the final value could be emitted out of a finalized output (vs the iteration output). The above works, but is a little hacky . The node-red flow JSON is attached below in the post. The major bummer of this reduce
design is that is not generic. It is tightly coupled to the *
function.
A generic impl would look something like:
The above is known imperfect. The key intent is that the *
function would no longer be coupled to the reduce
implementation. Callers could use it like:
In this case, I continue to use reduce
with *
for consistency. However, ideally reduce
users would be able to import it pipe iteration values through any number of composable nodes. Less code, more visual. Yada yada.
What do you think?
reduce/fold
[
{
"id": "e809845bab449b69",
"type": "tab",
"label": "reduce",
"disabled": false,
"info": "",
"env": []
},
{
"id": "a519499dfb7e0895",
"type": "function",
"z": "e809845bab449b69",
"name": "reduce",
"func": "/**\n * msg.input = [collection, init]\n */\n\nconst remaining = context.get('pending-processing')\n\nif (remaining) {\n node.log(JSON.stringify({ acc: msg.payload, remaining }))\n if (remaining.length) {\n const [hd, ...tl] = remaining\n context.set('pending-processing', tl)\n node.send({ __reduce__: true, payload: [msg.payload, hd] })\n } else {\n context.set('pending-processing', null)\n node.send({ __final__: true, payload: msg.payload })\n }\n} else {\n node.log(JSON.stringify(msg))\n const [collection, init] = msg.payload\n if (!collection.length) {\n node.send({ __final__: true, payload: init })\n }\n const [hd, ...tl] = collection\n context.set('pending-processing', tl)\n node.send({ __reduce__: true, payload: [init, hd] })\n}\n",
"outputs": 1,
"noerr": 0,
"initialize": "context.set('pending-processing', null)",
"finalize": "context.set('pending-processing', null)",
"libs": [],
"x": 310,
"y": 180,
"wires": [
[
"fa3c3aa82c4f52cc",
"6b08965615c9c3da"
]
]
},
{
"id": "fa3c3aa82c4f52cc",
"type": "function",
"z": "e809845bab449b69",
"name": "emit-needs-reduction",
"func": "if (msg && msg.__reduce__) {\n node.send(msg)\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 520,
"y": 120,
"wires": [
[
"fc6c5ed7f664f5c3"
]
]
},
{
"id": "6b08965615c9c3da",
"type": "function",
"z": "e809845bab449b69",
"name": "emit-final-reduction",
"func": "if (msg && msg.__final__) {\n node.send({ payload: msg.payload })\n}",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 510,
"y": 240,
"wires": [
[
"fa0ecd27d0532086"
]
]
},
{
"id": "fa0ecd27d0532086",
"type": "debug",
"z": "e809845bab449b69",
"name": "out",
"active": true,
"tosidebar": true,
"console": false,
"tostatus": false,
"complete": "true",
"targetType": "full",
"statusVal": "",
"statusType": "auto",
"x": 710,
"y": 240,
"wires": []
},
{
"id": "fc6c5ed7f664f5c3",
"type": "function",
"z": "e809845bab449b69",
"name": "acc ^ x",
"func": "// node.log(JSON.stringify(msg))\nconst [acc, curr] = msg.payload\nreturn { payload: acc * curr };",
"outputs": 1,
"noerr": 0,
"initialize": "",
"finalize": "",
"libs": [],
"x": 720,
"y": 120,
"wires": [
[
"a519499dfb7e0895"
]
]
},
{
"id": "0c4bd75423f8ce53",
"type": "inject",
"z": "e809845bab449b69",
"name": "init: fold-raise",
"props": [
{
"p": "payload"
}
],
"repeat": "",
"crontab": "",
"once": false,
"onceDelay": 0.1,
"topic": "",
"payload": "[[2,2,2,2,2,2,2,2,3], 1]",
"payloadType": "json",
"x": 90,
"y": 180,
"wires": [
[
"a519499dfb7e0895"
]
]
}
]