# Go deh!

Mainly Tech projects on Python and Electronic Design Automation.

## Friday, March 03, 2023

### Function purity and idempotence

Someone mentioned idempotence at work. I looked it up and noted that it too is a property of functions, like function purity.

I decided to see if I could write functions with combinations of those properties and embedded tests for those properties.

Running my resultant program produces this result:

Created on Fri Mar  3 18:04:09 2023

pure_idempotent.py

Explores Purity and idempotence with Python examples

Definitions:
Pure:
* Answer relies solely on inputs. Same out for same in.
I.E: `f(x) == f(x) == f(x) == ...`
* No side-effects.

Idempotent:
* The first answer from any input, if used as input to
subsequent runs of the function, will all yield the same answer.
I.E: `f(x) == f(f(x)) == f(f(f(x))) == ...`
* Any side effect of a first function execution is *preserved* on
subsequent runs of the function using the previous answer.

Side effect:
* A function is said to have side effects if it relies apon or modifies
state outside of that *given* by its arguments. Modifying mutable
arguments is also a side effect.

#--------

def remove_twos(arg: list[int]) -> list[int]:
"Returns a copy of the list with all twos removed."
return [x for x in arg if x != 2]

Function is:
Pure
Idempotent

#--------

def return_first_int(arg: int) -> int:
"Return the int given in its first call"
global external_state

if external_state is None:
external_state = arg
return external_state

Function is:
Impure! External state changed
Idempotent

#--------

def plus_one(arg: int) -> int:
"Add one to arg"
return arg + 1

Function is:
Pure
Non-idempotent! Output changes for nested calls

#--------

def epoc_plus_seconds(secs: float) -> float:
"Return time since epoch + seconds"
time.sleep(0.1)
return time.time() + secs

Function is:
Impure! Output changes for same input
Non-idempotent! Output changes for nested calls

### Code

The code that produces the above (but not its arbitrary colourising), is the following:

# -*- coding: utf-8 -*-
"""
Created on Fri Mar  3 18:04:09 2023

pure_idempotent.py

Explores Purity and idempotence with Python examples

Definitions:
Pure:
* Answer relies solely on inputs. Same out for same in.
I.E: `f(x) == f(x) == f(x) == ...`
* No side-effects.

Idempotent:
* The first answer from any input, if used as input to
subsequent runs of the function, will all yield the same answer.
I.E: `f(x) == f(f(x)) == f(f(f(x))) == ...`
* Any side effect of a first function execution is *preserved* on
subsequent runs of the function using the previous answer.

Side effect:
* A function is said to have side effects if it relies apon or modifies
state outside of that *given* by its arguments. Modifying mutable
arguments is also a side effect.
"""

import inspect

print(__doc__)

# %% Pure, idempotent.
print('\n#--------')

def remove_twos(arg: list[int]) -> list[int]:
"Returns a copy of the list with all twos removed."
return [x for x in arg if x != 2]

print(f"\n{inspect.getsource(remove_twos)}")
arg0 = [1, 2, 3, 2, 4, 5, 2]
print('Function is:')
print('  Pure' if remove_twos(arg0.copy()) == remove_twos(arg0.copy())
else '  Impure')
else 'Non-idempotent')

# %% Impure, idempotent.
print('\n#--------')

def return_first_int(arg: int) -> int:
"Return the int given in its first call"
global external_state

if external_state is None:
external_state = arg
return external_state

print(f"\n{inspect.getsource(return_first_int)}")
# Purity
external_state = initial_state = None
arg0 = 1
same_output = (return_first_int(arg0)) == return_first_int(arg0)
same_state = external_state == initial_state
print('Function is:')
if same_output and same_state:
print('  Pure')
else:
if not same_output:
print('  Impure! Output changes for same input')
if not same_state:
print('  Impure! External state changed')
# Idempotence
external_state = None
answer1, state1 = return_first_int(arg0), external_state
same_state = state1 == state2
if same_output and same_state:
print('  Idempotent')
else:
if not same_output:
print('  Non-idempotent! Output changes for nested calls')
if not same_state:
print('  Non-idempotent! External state changes for nested calls')

# %% Pure, non-idempotent.
print('\n#--------')

def plus_one(arg: int) -> int:
"Add one to arg"
return arg + 1

print(f"\n{inspect.getsource(plus_one)}")
# Purity
arg0 = 1
same_output = (plus_one(arg0)) == plus_one(arg0)
print('Function is:')
if same_output:
print('  Pure')
else:
print('  Impure! Output changes for same input')
# Idempotence
if same_output:
print('  Idempotent')
else:
print('  Non-idempotent! Output changes for nested calls')

# %% Impure, non-idempotent.
print('\n#--------')

import time

def epoc_plus_seconds(secs: float) -> float:
"Return time since epoch + seconds"
time.sleep(0.1)
return time.time() + secs

print(f"\n{inspect.getsource(epoc_plus_seconds)}")
# Purity
arg0 = 1
same_output = (epoc_plus_seconds(arg0)) == epoc_plus_seconds(arg0)
print('Function is:')
if same_output:
print('  Pure')
else:
print('  Impure! Output changes for same input')
# Idempotence
if same_output:
print('  Idempotent')
else:
print('  Non-idempotent! Output changes for nested calls')

END.