0

Just trying to understand what is and what isn't a side effect of a function in relation to functional programming. For example, if we have a very simple integer division function like this:

def integer_division(a, b, count=0):
    if (a < 0) ^ (b < 0):
        sign = -1
    else:
        sign = 1

    if  abs(a) - abs(b) == 0:
        return (count + 1) * sign
    elif abs(a) - abs(b) < 0:
        return count * sign
    else:
        return integer_division((abs(a) - abs(b))*sign, b, count + 1)

if __name__ == '__main__':
    print(integer_division(-5, 2))

Out: -2

Would sign variable be considered a side effect? On one hand it is only defined within the function, but then on the other hand it is entirely based on the function parameters and as such shouldn't be one. While I was pondering the question I re-wrote the function as this...:

def integer_division_diff(a, b, count=0, sign=1):
    if (a < 0) ^ (b < 0):
        sign *= -1

    if  abs(a) - abs(b) == 0:
        return (count + 1) * sign
    elif abs(a) - abs(b) < 0:
        return count * sign
    else:
        return integer_division((abs(a) - abs(b)) * sign, b, count + 1)

...which does exactly the same job and produces the same result, but which one is a more "correct" from a functional programming perspective?

EDIT: fixed a glaring issue when a is a multiple of b.

6
  • 3
    Altering a local variable isn't a side effect, because it does not affect anything outside the method. Commented Apr 14, 2021 at 12:03
  • 2
    What do You mean by side effect? it is a local variable Commented Apr 14, 2021 at 12:03
  • @khelwood, good, thanks. I was getting confused that any additional variables defined inside the function are considered side effects. So in this case either of these versions are fine? Commented Apr 14, 2021 at 12:05
  • 1
    Can you call the function with the same parameters and will it always return the same result? Can you set anything outside the function that would influence the result apart from its parameters? Does the function alter anything that's not its return value? — Yes, no and no. So your function is perfectly side-effect free. Commented Apr 14, 2021 at 12:06
  • 1
    A side effect is something observable from outside the function other than returning a value. So if your function modifies a pass-by-value variable, that is a side effect; if it writes to disk, that is a side effect; if it calls a restful api, that is a side effect. Commented Apr 15, 2021 at 3:34

1 Answer 1

1

This function is not problematic, but there is a discussion to be had.

sign has state and this state is managed by local side-effects in the shape of statements (which mutate the state). Now it's a very tiny local state, your brain is not going to explode, but there are other ways to write this function.

I am going to list some of them bellow and then I will try to make a statement. Note that I am not a pythonist myself so I let you judge what is more idiomatic.


def integer_division(a, b, count=0):
    sign = -1  if (a < 0) ^ (b < 0) else 1
    diff = abs(a) - abs(b)
    
    return (
        (count + 1) * sign if diff == 0
        else  count * sign if diff < 0
        else integer_division(diff * sign, b, count + 1)
    )
def integer_division(a, b, count=0):
    sign = -1 if (a < 0) ^ (b < 0) else 1
    diff = abs(a) - abs(b)
    done = diff <= 0

    if done:
      return (count + (diff == 0)) * sign
    else:
      return integer_division(diff * sign, b, count + 1)
def integer_division(a, b, count=0):
    sign = (1, -1)[(a < 0) ^ (b < 0)]
    diff = abs(a) - abs(b)
    done = diff <= 0

    return (
        lambda: integer_division(diff * sign, b, count + 1),
        lambda: (count + (diff == 0)) * sign
    )[done]()

(obviously don't create lambdas in a low level function, this is an example of how to use tuples as lazy conditional expressions)


Statements are not referentially transparent: a statement is not data, no variable can store what it evaluates to, you can't extract a single statement to a pure function, and when you are done reading a statement you can't move on to something else, because a statement is incomplete: it is linked to something else that you must look up and carry with you at the back of your head until it's finally out of scope.

Removing this kind of cognitive load is one of the things functional programming aims at, and expressions make it possible because they are self-contained.

Python doesn't have constants, but you can chose to never reassign variables as a convention and to prefer expressions over statements (with reason). Do this and your brain's backpack will get lighter because you will allow yourself to forget about how values came into existence. Your names will describe decisions that have been made, not decisions in the process of being made and that is invaluable.

Whether you find the propositions above to your liking or not, the fact is they make the key elements of the algorithm stand out. You can short-circuit your reading, trust that the right-hand side of assignments is correct and go on your merry way instead of digging for informations.

Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.