Search code examples
javascriptpythonpass-by-value

Mimicking Pass-By-Reference in Python/Javascript Using Wrappers - Good Practice?


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?

Solution

  • 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.