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"
]
]
}
]




