-1

Say I want to modify a number or some other primitive inside of a function. For example, something like this (note: pseudocode):

// apply fn to every value in a tree, in-order traversal
function treeReduce (tree, fn, result):
    if (tree == undefined) return
    treeReduce(tree.left, fn, result)
    result = fn(result, tree.value)
    treeReduce(tree.right, fn, result)

sum = 0
treeReduce(myTree, +, sum)

obviously this won't work, because result is just being reassigned, and the sum passed in won't see the modifications. So a common way I have gotten around this (in any pass-by-value language like Python or Javascript) is using wrappers:

// apply fn to every value in a tree, in-order traversal
function treeReduce (tree, fn, result):
    if (tree == undefined) return
    treeReduce(tree.left, fn, result)
    result[0] = fn(result[0], tree.value)
    treeReduce(tree.right, fn, result)

sumWrapper = [0]
treeReduce(myTree, +, sumWrapper)

However, I recently searched the internet to see if this is a common pattern, and can't find much information about it. Specifically, I'd like to know three things:

  1. is this a common pattern?
  2. is this good practice?
  3. if not, any alternatives?
woojoo666
  • 7,801
  • 7
  • 45
  • 57
  • It doesn't seem to be a bad practice, it resembles the monads of functional programming, as they are wrappers. I would pass a reducer function. Also consider creating an iterator, it can be useful for other things. – David Lemon Nov 15 '18 at 08:21
  • what do you mean by passing a reducer function? Isn't this already passing a reducer function `fn`? – woojoo666 Nov 16 '18 at 02:33

1 Answers1

2

It could be done like that, however it increases "sideeffects" of your function which most coders would advise to minimise as much as possible. Instead you could use the function's return value for it. Your code would then still pass the "previous" (or "start") value as a primitive, but return the result.

Here is how it would look in JS (I took a less trivial fn to demonstrate it performs an in-order execution):

// apply fn to every value in a tree, in-order traversal
function treeReduce (tree, fn, start) {
    if (tree === undefined) return start
    let result = treeReduce(tree.left, fn, start)
    result = fn(result, tree.value)
    result = treeReduce(tree.right, fn, result)
    return result
}

let myTree = { value: 1, left: { value: 2 }, right: { value: 3 } }

let result = treeReduce(myTree, (a,b) => a*a+b, 0)
console.log(result)

Note that the above can be written more concisely now:

// apply fn to every value in a tree, in-order traversal
function treeReduce (tree, fn, start) {
    return !tree ? start
        : treeReduce(tree.right, fn, fn(treeReduce(tree.left, fn, start), tree.value))
}

let myTree = { value: 1, left: { value: 2 }, right: { value: 3 } }

let result = treeReduce(myTree, (a,b) => a*a+b, 0)
console.log(result)

In Python:

import collections
Tree = collections.namedtuple('Tree', ['value', 'left', 'right'])

# apply fn to every value in a tree, in-order traversal
def treeReduce (tree, fn, start):
    return start if not tree else ( 
        treeReduce(tree.right, fn, fn(treeReduce(tree.left, fn, start), tree.value))
    )

myTree = Tree(1, Tree(2,None,None), Tree(3,None,None))

result = treeReduce(myTree, lambda a,b: a*a+b, 0)

print(result)

Both JS and Python also allow to extend this to the case where you would need to set multiple primitive values: functions can return arrays/lists/tuples/objects, which can then be assigned by unpacking/destructuring them into separate variables.

trincot
  • 317,000
  • 35
  • 244
  • 286
  • I am not sure if that would work, and it's a bit hard to explain why, so sorry if I get it wrong. You aren't passing the accumulator (in this case, `result`) to the right side of the tree. The reducer `fn` takes in the accumulator `result` and a node value, but you are calling it with `result` and the reduction of `tree.right` – woojoo666 Nov 16 '18 at 02:30
  • though I can see now that it probably can be done with a return value instead of side effects, it will probably require a helper function, because you somehow have to pass the accumulator to the right side of the tree, so the helper function needs to take it in as an argument – woojoo666 Nov 16 '18 at 02:32
  • True, I had taken the addition from your example, but to have in-order accumulation, you would have to pass the previous value as argument. I updated my answer. – trincot Nov 16 '18 at 08:01
  • ok so I was partially right, you had to pass the accumulator in as an argument, but you didn't need a helper function, which is nice. I feel like the "concise" version is kinda hard to read, but the first version has comparable readability as my original wrapper-based code, and has no side-effects, so I'm going to accept this answer. I have a feeling there are other examples that are not as easy to convert to return-style, but I will have to think about it more – woojoo666 Nov 16 '18 at 14:05