Compositional Object-Oriented Prototypes
Plan of the Talk
- Introduction: OO, Informally
- Minimal OO, Formally
- Wait, what?
- What about my favorite OO feature?
- Inheritance: Mixin, Single, Multiple
- Conclusion: Making sense of OO
Introduction: OO, Informally
Claims
OO is Internal Modular Extensibility
We rebuild OO atop pure FP from these first principles
Classes, mutation, objects(!) are not fundamental
Key ignored semantic notion: Conflation
Mixin Inheritance: most fundamental, not most modular
Best Inheritance: combine Single and Multiple
Introduction: OO, Informally
What OO is not about
C++ (or even Java, etc.)Classes“Encapsulation”, “Information Hiding”Opposite of FP, Inheritance vs CompositionMutable records everywhereAsynchronous Message Passing everywhereUML / Co-Algebras / Relational ModelingIntroduction: OO, Informally
What OO is about
Modularity: dev needs little knowledge in
Extensibility: dev needs little knowledge out
Language-internal: first-class or second-class
Support for Division of Labor
Mechanism: Inheritance
Introduction: OO, Informally
Internal vs External
First class: internal, runtime (values, some via reflection)
Second class: internal, compile-time (types, macros)
Third class: external, software (tooling)
Fourth class: external, wetware (design patterns)
Compositional Object-Oriented Prototypes
Plan of the Talk
- Introduction: OO, Informally
- Minimal OO, Formally
- Wait, what?
- What about my favorite OO feature?
- Inheritance: Mixin, Single, Multiple
- Conclusion: Making sense of OO
Minimal OO, Formally
Minimal First-Class Semantics
You only really understand it if you can program it
Second-class is just first-class at compile-time
Any semantics is necessarily first-class for someone
A non-minimal model means you’re still confused
Minimal OO, Formally
Minimal First-Class Extensibility: What
Values: V
Extensions: V → V
e.g. for type V = Record
(define point-p (record (x 2) (y 4)))
(define (paint-blue p)
(extend-record p 'color 'blue))
Minimal OO, Formally
Minimal First-Class Extensibility: How
Good: Apply extensions
(ext value)
Better: Compose extensions
(compose ext1 ext2)
... important if restricted extension language
Minimal OO, Formally
Minimal First-Class Extensibility: Top
To get V
from V → V
, apply to top value ⊤
Typical choices:
V = Record
, ⊤ = (record)
V = Number
, ⊤ = 0
V = Pointer
, ⊤ = null
In pure partial lazy FP, ⊤ = (lazy ⊥)
Bad: use fixpoint combinator Y : (V → V) → V
Minimal OO, Formally
Minimal First-Class Modularity: Records
Values: V
, W
, X
...
Identifiers: I
, J
.
Set of bindings, a.k.a. record, a.k.a. indexed product:
∏R = i:I → Rᵢ
Example:
R = {x: Number, y: Number, color: Symbol}
(define point-q
(record (x 3) (y 4) (color 'blue)))
Minimal OO, Formally
Minimal First-Class Modularity: Spec
Module context: ∏M = i:I → Mᵢ
Modular specification for X
: ∏M → X
Example:
M = { ls: Str → List(Str),
sort: List(Str) → List(Str) }
(define (ls-sorted m)
(compose (m 'sort) (m 'ls)))
Minimal OO, Formally
Minimal First-Class Modularity: What
modular module specification:
Given a common module context ∏M
,
for each identifier j
, a value of type Xⱼ
:
∏M → ∏X = ∏M → j:J → Xⱼ
∏M
module context, record of identifiers referenced
∏X
module body, record of identifiers defined.
Minimal OO, Formally
Minimal First-Class Modularity: Y
Close modular spec: ∏M → ∏M
every identifier referenced is defined
Extract target record
∏M
from spec
∏M → ∏M
?
link references, close open loops, tie knots, …
fixpoint combinator Y
Minimal OO, Formally
Digression: Scheme vs FP
Issue 1: pure applicative Y sucks
Solution: stateful Y, lazy Y, or second class Y
Issue 2: unary functions are syntax-heavy
Solution: cope, autocurry, or multiple arities with care
coop.rkt
choices: stateful Y (letrec), autocurry
Minimal OO, Formally
Minimal First-Class Modular Extensibility
Extensions: X → X
Modularity context: ∏M
Open modular extensible spec: ∏M → ∏(X → X)
Close modular extensible spec: ∏M → ∏(M → M)
Resolve each binding — apply to top value ⊤
Target from reduced modular spec ∏M → ∏M
— use Y
Minimal OO, Formally
Minimal “Object-Orientation”
(define (instantiate spec)
(Y (λ (m i) (spec m i ⊤))))
(define (inherit child parent m i)
(compose (child m i) (parent m i)))
That's mixin inheritance, all the OO you need!
(deftype spec (Fun M → I → X → X))
(define (my-spec self method-id super)
...value)
Minimal OO, Formally
Minimal Example
(define (coord-spec self i super)
(case i ((x) 2) ((y) 4) (else super)))
(define (color-spec self i super)
(case i ((color) 'blue) (else super)))
(define point-p (instantiate
(inherit coord-spec color-spec)))
(point-p 'x) ⇒ 2
(point-p 'color) ⇒ blue
Minimal OO, Formally
Minimal Example, non-trivial inheritance
(define (add-x-spec dx self i super)
(case i ((x) (+ dx super))
(else super)))
(define (rho-spec self i super)
(case i ((rho)
(sqrt (+ (sqr (self 'x))
(sqr (self 'y)))))
(else super)))
Minimal OO, Formally
Minimal Example, non-trivial inheritance (test)
(define point-r (instantiate
(inherit (add-x-spec 1)
(inherit coord-spec
rho-spec))))
(point-r 'x) ⇒ 3
(point-r 'rho) ⇒ 5
Compositional Object-Oriented Prototypes
Plan of the Talk
- Introduction: OO, Informally
- Minimal OO, Formally
- Wait, what?
- What about my favorite OO feature?
- Inheritance: Mixin, Single, Multiple
- Conclusion: Making sense of OO
Wait, what?
What did we just do?
Reconstructed recognizable OO from first principles
The first principles: first-class, modularity, extensibility
OO literally in two short definitions, in any FP language
No classes, no mutation, no objects!
How is it even possible???
Wait, what?
Precedents
Yale T Scheme 1982, YASOS 1992 (applicative, stateful)
Bracha & Cook 1990: theoretical “model” of inheritance
GCL 2004: Runs all Google (lazy, dynamic scope)
Jsonnet 2014: GCL cleanup (lexical scope, JS-y syntax)
Nix extensions 2015: isomorphic to above, OO in 2 funs
Wait, what?
OO without objects (as in Yale T Scheme)
“object” is any value, “instance” is just a regular record
No inheriting from an “instance”
“component” is a specification
No computing methods from a “component”
No equivalent to object (or class) in other OO languages!
... contrast with GCL, Jsonnet, Nix, that have objects(!?)
Wait, what?
Conflation: hidden product, implicit cast
Prototype = Spec × Target
Target = lazily resolved fixpoint from spec
Want to compute a method? Use the target
Want to inherit? Use the spec
Lazy is essential: most specifications are partial
Prototype OO: Ani 1976, ThingLab 1977, SELF 1986,
JavaScript 1995, GCL 2004, Jsonnet 2014, Nix 2015
Compositional Object-Oriented Prototypes
Plan of the Talk
- Introduction: OO, Informally
- Minimal OO, Formally
- Wait, what?
- What about my favorite OO feature?
- Inheritance: Mixin, Single, Multiple
- Conclusion: Making sense of OO
What about my favorite OO feature?
What about Classes?
Class = (Second-class) Prototype for Type (+ methods)
Object, Instance = element of the target type
static methods = methods of the target type
object methods = static methods applied to the element
abstract class = used only for its spec
concrete class = used only for its target
C++ templates: pure lazy FP Prototypes at compile-time
What about my favorite OO feature?
What about Objects?
T: "object" is any value, "instance" is target from spec
Prototype OO: "object / instance" is Spec × Target
Class OO: "object / instance" is Target type element
"object" and "instance" are very ambiguous words
"object" as a concept is not necessary for OO
Fields are named first, understood much later.
What about my favorite OO feature?
What about Types?
Spec Subtyping:
before fixpoint
Target Subtyping:
after fixpoint
SUBTYPING and FIXPOINT DO NOT COMMUTE!
Fortress 2011 Type checking modular multiple dispatch
with parametric polymorphism and multiple inheritance
Scala 2012 Dependent Object Types
Prototype OO? Dunno, maybe dependent types?
What about my favorite OO feature?
What about Typeclasses or Modules?
Haskell Typeclass, Rust Trait, ML modules...
modular but not extensible
Contra Cook: extensibility matters!
Interface Passing Style 2012: extensible typeclasses
Isomorphism via macros:
class ≃ typeclass
(linear) pure ≃ stateful
What about my favorite OO feature?
What about side-effects?
Classes: pure lazy Prototype OO at compile-time
Target types historically mutable, but don’t have to be
Plenty of pure object libraries in Lisp, Java, Scala…
Mutable inheritance: semantics is hard, like all mutation
CLOS update-instance-for-redefined-class
invalidate caches (atomically?)
What about my favorite OO feature?
Multiple dispatch? Method combination?
both: LOOPS 1986, CLOS 1991, Dylan 1992
multiple dispatch only: Cecil 1992, Fortress 2006, Julia 2012
“generic functions”, see CLOS for docs and experience
see Fortress for proper types
Pure FP vs orphan/friend/mutually-def'd (type)classes?
Global fixpoint of entire namespace (nixpkgs…)
What about my favorite OO feature?
What about single or multiple inheritance?
Worth a section of its own (see next)
Lowdown:
mixin inheritance is simplest
single inheritance is most efficient, least expressive
multiple inheritance is most modular
You can have the best of them together!
Compositional Object-Oriented Prototypes
Plan of the Talk
- Introduction: OO, Informally
- Minimal OO, Formally
- Wait, what?
- What about my favorite OO feature?
- Inheritance: Mixin, Single, Multiple
- Conclusion: Making sense of OO
Inheritance: Mixin, Single, Multiple
Single Inheritance
Hoare 1966 (sub)classes (handwaving)
SIMULA 1967 “Prefix classes” (first implementation)
Smalltalk 1976 “single inheritance” (compromise)
C-with-classes 1979, Java 1996, C# 2000,
COBOL 2002, JavaScript 2015...
Very efficient, simplest for 1960s before cheap lambdas
Least expressive, least modular
Inheritance: Mixin, Single, Multiple
Multiple Inheritance
KRL 1975 “inheritance of properties” Ani 76, ThingLab 77
Method conflict if in parallel superclasses:
Mesa 1979, SELF 1986, C++ 1989, ADA 2003
Method combination after linearizing inheritance DAG:
Flavors 1979, LOOPS 1986, CLOS 1991
Simple combination, can only call super method:
Ruby 1992, Python 1994, Scala 2004
Most expressive, most modular
Most complex (~100 loc), somewhat inefficient in general
Inheritance: Mixin, Single, Multiple
Mixin Inheritance
Yale T Scheme 1982: implemented, not conceptualized
Bracha/Cook 1990: conceptualized, not implemented
PLT Scheme 1998, Strongtalk 2002, GCL 2004, Newspeak 2006, Jsonnet 2014, Nix 2015
Simplest in FP, more expressive & modular than Single
As inefficient as Multiple Inheritance, less modular
Inheritance: Mixin, Single, Multiple
Mixin more Expressive than Single
Second-class OOP: expressiveness issue.
Single only cons
a spec, not append
spec
Less sharing, more duplication, extra hoops
… maintenance nightmare.
First-class OOP: your own mixins on top (PLT 1998)
Still extra hoops, extra concepts, extra complexity
Inheritance: Mixin, Single, Multiple
Multiple More Modular than Mixin
Spec depends on others to avoid source duplication
Can’t “just” pre-append: runtime duplication, bad,
exponential, non-commutative, non-idempotent
Mixin inheritance: manually manage linearization
A spec’s inheritance DAG becomes part of its interface
Nightmare on transitive dependency change
Multiple inheritance: just declare direct superspecs
No maintenance nightmare, just bliss.
Inheritance: Mixin, Single, Multiple
Single and Multiple Together
Two separate hierarchies (lame): CLOS (struct/class)
Same hierarchy (yay): Ruby (class/module),
Scala (class/trait), Gerbil Scheme (struct/class)
“struct” can inherit from “class” and vice-versa
struct only in “single inheritance” wrt other structs
“tail property”: the property actually needed for efficiency
a struct’s class linearization is a suffix to its subclasses’s
Compositional Object-Oriented Prototypes
Plan of the Talk
- Introduction: OO, Informally
- Minimal OO, Formally
- Wait, what?
- What about my favorite OO feature?
- Inheritance: Mixin, Single, Multiple
- Conclusion: Making sense of OO
Conclusion: Making sense of OO
Claims (Redux)
OO is Internal Modular Extensibility
We rebuild OO atop pure FP from these first principles
Classes, mutation, objects(!) are not fundamental
Key ignored semantic notion: Conflation
Mixin Inheritance most fundamental Inheritance
Best: combine Single and Multiple Inheritance
Conclusion: Making sense of OO
This very Presentation!
scribble
+ coop.rkt
spit HTML for reveal.js
No objects, just specs and targets, mixin inheritance
FP+OO system in 350 loc incl. autocurry, comments
Prototypes for slides w/ toc in ~125 loc
How can TOC appear purely before the end?
Fixpoint magic! Lazy avoids exponential recomputation
… Time for demo?
Conclusion: Making sense of OO
Why did I care?
Great Usenet Flamewars of 1990s:
Dysfunctional Programming vs
Objectionable Disorientation
Talking Past Each Other… for Decades
1970s Lisp had both OO and FP
2000s Fortress, Scala: with types
Opposition in Mind, not in Reality
Conclusion: Making sense of OO
Why So Much Blindness?
Industry doesn’t care enough about Correctness
Academia doesn’t grok Programming In The Large
Both ignore the Human Factor:
interchangeable cogs, transient students
All get stuck in their paradigms
Conclusion: Making sense of OO
Two Mindsets
Compositionality for reducible problems
Extensibility for irreducible problems
Brighter than ambitious vs more ambitious than bright
History: OO invented to model complex SIMULAtions
History: FP to formalize simple computations in logic
Conclusion: Making sense of OO
Meta-Thesis
Humility, not fanaticism
Incommensurable paradigms? Go wider!
Researcher: old paradigm’s low-hanging fruits harvested
Simplicity matters
Racket, Scheme: λ’s for Semantics, macros for Syntax
Conclusion: Making sense of OO
Thank You!
Hire me — or be hired by me! <fare@mukn.com>
Plenty more research ideas, code and papers to write…