div.ProseMirror

Dr. Schmood's Notebook of Python Calisthenics and Orthodontia

Don't get bit by misaligned state and output

Sharing Information

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)inline_formula not implemented

Sharing Information in 1977

Hidden State

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

What Does = Equal?

  1. Reflexive:inline_formula not implemented

  2. Transitive: inline_formula not implemented

  3. Symmetric:inline_formula not implemented

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

The Case For ≔
Niklaus Wirth posing with the Lilith Workstation

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 :=
--- 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;
Reasoning About State

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.inline_formula not implemented

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)
(print c)
0.3s
Clojure
nil
  • 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)
0.0s
Clojure
"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=6 # 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 + y)(1)
7

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
0.4s
Clojure
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)
0.3s
Clojure
2

Expressions of State

In general we like to favor immutability where sensible.

~ Pandas Documentation (emphasis theirs)

artwork_data.csv
Bytes

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
True
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[1, 'acquisitionYear']))
df_updated = \
df.assign.loc[1, 'acquisitionYear'](inc(df.loc[1, 'acquisitionYear']))
df_updated.loc[1, 'acquisitionYear'] <= 1922
False

Immutability and Declarative Syntax

Dr. Dobb's Journal of Computer Calisthenics & Orthodontia, May 1976 (page 136)

Writing for loops and publishing code for copy and paste is a 1970s approach.

Instead:

  1. Mutation: mutable assignments → default to immutability globally

  2. Syntax: imperative statements (for) → declarative expressions (Pandas, maps)

  3. Publish computational environments and data.inline_formula not implemented

Thank You

https://nextjournal.com/schmudde/dr-schmoods-notebook

-

Appendix

inline_formula not implementedSpecial thanks to the Dr. Dobb's Archive at 6502.org

inline_formula not implemented≔ in Python 3 allows assignment while also evaluating an expression:

Current:
    env_base = os.environ.get("PYTHONUSERBASE", None)
    if env_base:
        return env_base
Improved:
    if env_base := os.environ.get("PYTHONUSERBASE", None):
        return env_base
Python

inline_formula not implementedNextjournal makes code runnable by applying these approaches to all levels of the stack. The notebook interface is a departure from IDEs of the past. Furthermore, it's not just the software you write that is immutable, it's the entire computation stack, down to the operating system. The data that drives the code is also content addressed and immutable. This makes running code in the cloud reliable, reproducing results effortless, and cloud collaboration possible.

See Not everything is an expression by Michael Robert Arntzenius for a discussion on syntax classes.

Runtimes (2)