What is my state?

Vesa Karvonen

Reaktor

About Me

  • Senior programmer
    • ~27 years of programming
    • ~21 years since professional
    • ~16 years of "functional first" programming
    • Computer Graphics, Games, ... Web Apps are new to me!
  • Special interest in programming languages and techniques
    • Type systems, semantics, compilers
    • Domain Specific Embedded Languages

Poll

Stateless vs Stateful?

Programs are about state. It can't be completely eliminated. It must be managed.

Mutable state vs Immutable state?

State changes over time so immutable state is an oxymoron. One can capture state at a point as an immutable value but that is not all.

Global state vs Local state?

One of the points of this talk is to argue that external shared global state is actually better than internal encapsulated local state.

What is this talk about? (1/2)

There are no new "innovations" in this talk.

Most papers in computer science describe how their author learned what someone else already knew. — Peter Landin

What is this talk about? (2/2)

  • My experience with Calmm
    • An architecture and libraries for writing Reactive UIs
    • Encourages use of external shared state and continuous observable properties
  • Why does Calmm work?

Goals

  • Think about state — hopefully in new ways 🤔

What is state?

State

  • Has value
  • Has identity
  • Can change over time

State is hard

State complects: Value and Identity and Time

Both identity and time are subtly complex!

Tracking identities often complicates algorithms e.g. React keys.

Change over time invalidates computations dependent on state.

Bugs, bugs, and more bugs!

Support in typical language

  • Mutable variables and mutable fields in objects
  • Fundamentally second-class
    • Cannot be returned from functions
    • Cannot be passed as parameters
  • Cannot be composed
  • Cannot be decomposed
  • Cannot be observed for changes (over time)

Limitations (1/2)


let x = 1       // Creates mutable state
let y = 2
let sum = x + y // Takes snapshots of state

x = 3

sum             // No longer valid
            

Limitations (2/2)


function foo() {
  let x = 1 // Creates mutable state

  bar(x)    // Doesn't pass state - only value

  x = 2     // Mutates state - not visible to `bar`

  return x  // Doesn't return state - only value
}
            

Making state fun again!

Interface for state


interface State<T> {
  get(): T;
  set(value: T): void;
}
            

First-class state


class Atom {
  constructor(value) {
    this.value = value
  }
  get() {
    return this.value
  }
  set(value) {
    this.value = value
  }
}
            

First-class: can pass as arguments and return as results

Program components can declare state as parameters

Observable state


class Atom {
  constructor(value) {
    this.value = value
    this.observers = []
  }
  get() { return this.value }
  set(value) {
    this.value = value
    this.observers.forEach(observer => observer(value))
  }
  subscribe(observer) {
    observer(this.get())
    this.observers.push(observer)
  }
}
            

Independence from time

Decomposable state


class LensedAtom {
  constructor({getter, setter}, source) {
    this.getter = getter
    this.setter = setter
    this.source = source
  }
  get() {
    return this.getter(this.source.get())
  }
  set(value) {
    this.source.set(this.setter(value, this.source.get()))
  }
}
            

Store state as a whole and slice elements of said state to components

Composable state


class PairAtom {
  constructor([lhs, rhs]) {
    this.lhs = lhs
    this.rhs = rhs
  }
  get() {
    return [this.lhs.get(), this.rhs.get()]
  }
  set([lhs, rhs]) {
    this.lhs.set(lhs)
    this.rhs.set(rhs)
  }
}
            

Transactionality

Independence from storage

The case for global state

Why global state?

  • Components can then be stateless and easily composed
  • Global state can be inspected
  • Operations on global state can be tested simply
  • Robust "single source of truth"

Why not local state?

  • Local state is inaccessible
  • Makes composition harder
  • Can only be tested indirectly
  • Diverges easily

Common misconseptions

Streams are stateless

  • Is this stateless?
  • Merge + Scan introduces local state
  • Diverges easily
  • Timing is significant
  • On the positive side
    • Observable
    • Can make deps more explicit: "What contributes to this stream?"
      • But not necessarily.

Anyone can mutate state — Mess

Yes: Anyone who has access to a slice of state can mutate it.

But

Scope: A component that is given a slice of state as a parameter can only mutate that slice — not everything.

Intent: If you give mutable state to a component, the intention is that the component can mutate it.

Observe: Even if someone mutates the state, components can observe the state change and act accordingly.

Conclusion

Homework

  • Think about where you want to store state
  • And how to keep components consistent with state

Questions?