# Dr. Schmood's Notebook of Python Calisthenics and Orthodontia

The massive information processing power [...] will truly become available to the general public. And, I see that as having a tremendous democratizing potential, for most assuredly, information — data and the ability to organize and process it — is power.

~ Jim Warren Dr. Dobbs Journal Vol 1 (January 21, 1977)

## Hidden State

The Hidden State problem is a problem with Python and not necessarily notebook interfaces.

## What Does = Equal?

1. Reflexive:
2. Transitive:
3. Symmetric:

### Hidden State by Mutation

Assignment (=) and Equality (==)

#### Strings

a="10"
b=a
a=a+"0"
str("a: " + a + " b: " + b)
'a: 100 b: 10'

An unbalanced operation.

id(a) == id(b)
False

Immutable sequences: Strings, Tuples, Bytes

#### Lists

a=["10"]
b=a
a[len(a):] = ["20"]
str("a: " + str(a) + " b: " + str(b))
"a: ['10', '20'] b: ['10', '20']"

A balanced operation.

id(a) == id(b)
True

Mutable sequences: Lists, Byte Arrays

#### State and Syntax

You may call me by my name, Wirth, or by my value, Worth.

~ Niklaus Wirth

##### The Case For ≔

A notorious example for a bad idea was the choice of the equal sign to denote assignment. It goes back to Fortran in 1957 and has blindly been copied by armies of language designers. Why is it a bad idea? Because it overthrows a century old tradition to let “=” denote a comparison for equality, a predicate which is either true or false. But Fortran made it to mean assignment, the enforcing of equality. In this case, the operands are on unequal footing: The left operand (a variable) is to be made equal to the right operand (an expression). x = y does not mean the same thing as y = x.

~ Niklaus Wirth, Good Ideas, Through the Looking Glass

--- Ada 95
procedure Main is
A, B : Integer := 0;
C    : Integer := 100;
D    : Integer;
begin
if A = 0 and C = 100 then
A := A + 1;
D := A + B + C;
end if;
end Main;

a=42Set a equal to 42 or a equals 42.

If either side of the equation changes, the other side must accordingly change.

a:=42a is assigned the value of 42.

a becomes 42, but 42 does not become a. 42 is immutable, it will always be 42.

The thing on the right side will exist until nothing points to it, at which point it is garbage collected.

##### The Case For Define
(def c)
user/c
• Assignment: to specify a correspondence or relationship
• Define: to identify the essential qualities
(def a "10")
(def b a)
(def a (str a "0"))
(str "a: " a " b: " b)
"a: 100 b: 10"
a="10"
b=a
a=a+"0"
str("a: " + a + " b: " + b)
'a: 100 b: 10'

### Hidden State by Scope

#### Referential Transparency

x=1 # Introducing state

def inc():
global x
x+=1 # Modifying state
return x

print("x: %s, inc(): %s" % (x, inc()))
print("x: %s, inc(): %s" % (x, inc()))

inc() == inc()
False
y=1 # Introducing state

def inc(y):
y+=1 # Modifying state
return y

print("y: %s, inc(): %s" % (y, inc(y)))
print("y: %s, inc(): %s" % (y, inc(y)))

inc(y) == inc(y)
True
def inc(z):
z+=1
return z

print("1: 1, inc(): %s" % (inc(1)))
print("1: 1, inc(): %s" % (inc(1)))

inc(1) == inc(1)
True

#### Can Seem Innocuous

(lambda a: a + 1)(1)
2
(lambda a: a + x)(1)
6

#### Local Bindings

let [a 2] → make the symbol a the integer 2. a is not a variable, it will never change.

(let [a 2]
(println "Lexical scope: " a))

(println " Global scope: " a)

(var a) ;; return the var itself, not its value
user/a

The scope is totally defined by the let form.

(let [i 2]
(try
(eval '(var i))
(catch Exception e
(print "Exception:" (.getMessage e))))
i)
2

Result: Cells where state is easy to reason about.

((fn fib [a b]
(lazy-seq (cons a (fib b (+ a b)))))
0 1)
(lambda x: x + 1)(2)
3

## Expressions of State

In general we like to favor immutability where sensible.

~ Pandas Documentation (emphasis theirs)

artwork_data.csv

### Immutability

import pandas as pd

artwork_data = pd.read_csv(artwork_data.csv) # Introducing in-memory state

print(list(artwork_data))
artwork_data.drop(columns=["accession_number"])
print(list(artwork_data))

### Explicit Mutability

print(list(artwork_data))
artwork_data.drop(columns=["accession_number"], inplace=True)
print(list(artwork_data))

### Data Mutation

print(artwork_data.at[1, 'acquisitionYear'])
artwork_data.head()

### Pure Function

inc(artwork_data.at[1, 'acquisitionYear'])
1923.0

### Implicit Mutability

Run in one order, this cell returns True. Run in another order, this cell returns False.

print(inc(artwork_data.at[1, 'acquisitionYear']))
artwork_data.at[1, 'acquisitionYear'] <= 1922
False
artwork_data.at[1, 'acquisitionYear'] = inc(artwork_data.at[1, 'acquisitionYear'])
print(inc(artwork_data.at[1, 'acquisitionYear']))
artwork_data.at[1, 'acquisitionYear'] <= 1922
False

## Guaranteed Immutability

### Static Frame

import static_frame as sf
import pandas as pd

artwork_data = pd.read_csv(artwork_data.csv) # Introducing in-memory state

df = sf.Frame.from_pandas(artwork_data)
df['artist': 'year'].head(2)

### A Change in Data is New Data

This will always be True.

print("Original: " + str(df.loc[1, 'acquisitionYear']))
df.assign.loc[1, 'acquisitionYear'](inc(df.loc[1, 'acquisitionYear']))
df.loc[1, 'acquisitionYear'] <= 1922
True

df and df_updated are not equivalent.

print("Original: " + str(df.loc[69196, 'acquisitionYear']))
df_updated = df.assign.loc[69196, 'acquisitionYear'](inc(df.loc[69196, 'acquisitionYear']))
df_updated.loc[69196, 'acquisitionYear'] <= 1922
False

## Immutability and Declarative Syntax

Writing for loops and publishing code for copy and paste is a 1970s approach.
1. Mutation: declarations (def, x=5): default to immutability globally
2. Syntax: declarative expressions (maps) > imperative statements (for loops)