-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathTODO
219 lines (146 loc) · 15.2 KB
/
TODO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
Proposed renaming:
proposed new name, name in current paper, other possibilities, meaning
target, instance, (computation, value, fixed-point, self, result), the result (?value of the) computation that was incrementally specified
mixin, prototype, (increment, specification, spec, component, trait), the increment of computational specification
prototype, object, (mixin, trait), the conflation of mixin and target (prototype and instance) in a single entity
wrapper, prototype function, (), the composable function that takes the partial computation so far and extends it
element, class instance, (object), what is called "object" in class OO, an element of the type incrementally specified as a class (that is itself the prototype/object whose target/instance is a type)
record, record, (structure, instance, labeled product, table, object), a mapping from name/symbol to values, that typically serves as both targets and elements above, but can be considered without any OO
tagged record,,, a record with special entries (mapping for special labels?) for the metadata such as the mixin used to compute the record or the variant of a sum type or caches used by the MOP.
It's not "recent research" so much as simplification and extraction of the essence of OO, as developed since the late 1960s, reduced to its core and implemented since the late 1970s, formalized since the late 1980s, made ubiquitous since the late 1990s, rediscovered in a pure functional setting in the late 2000s, and finally done right since the late 2010s.
Is a Computation "just" a Promise?
No. Promise maps Computation inside Value, just like Literal (or Quote) maps Value into Computation.
How do you deal *purely* with adding (multi)methods to *other classes*
to deal with newly [dr]efined aspects?
By manipulating not one class at a time, but an entire configuration DAG of classes.
Keeping all your code in a vast open recursion until the very last moment.
Now doing it in a pure way means that instead of a single global context that you side-effect,
you can have many lexical variants of context that you update non-destructively.
Being able to use multiple variants is a great power, that comes with great responsibility.
Furthermore, making this context explicit, makes you realize that beyond the language core,
you're growing an ecosystem-wide namespace of library items and conventional extension-points,
across all user packages.
Hopefully, the class namespace hierarchy is well-integrated with your language's module system
so users don't have to learn multiple parallel naming conventions just to use anything.
If you've built large configurations with Nix (or Jsonnet, GCL, etc.) you are probably familiar
with open recursion with a large context, how powerful the paradigm is, and
how quickly things can get out of hand in absence of strong curation of the context namespace.
If you want let extenders add methods to Num for the new aspects they handle (and you usually do),
then yes even simple arithmetics remains open until the last.
Just like in NixOS you have to consistently modify several packages
at different places in the package DAG.
Thus, cultivating the naming conventions for the DAG is
an essential part of the language ecosystem development process.
Whether this DAG is unique/global/implicit and side-effected,
or duplicatable/lexical/explicit and modified purely.
But doing it purely forces you to be more explicit about this cultivation indeed.
The context namespace is for the entire ecosystem and not just for a single program.
Beware of multiple inconsistent copies of the context: you have the power to make them,
and the responsibility to manage them. Also, it can be a very heavy thing to duplicate.
When you realize you are extending one point in the DAG, it becomes more obvious
that extension is through a lens in a larger object. Such goes for updating a method
within a method combination of a generic function involving multiple classes
and/or singleton objects in a hierarchy.
The single hierarchy also makes the need for conflation more obvious,
as a same name/path can denote both prototype and instance.
Slide(s) to explain why multiple inheritance matters:
- Show a dependency graph between mixins, with module boundaries.
- Show that mixin style forces users to write the entire list, in order.
- Show that mixin style forces users to write the entire list, in CONSISTENT order, or else.
- Show that mixin style forces downstream module users to rewrite their list when yours change.
- Show that mixin style forces downstream module users to make changes based on indirect dependencies they didn't know of.
- Sure you can explicitly call some kind of compute-dependency-list function...
or then again you should use multiple inheritance which does it for you,
except it's more declarative and you can't get it wrong by accidentally forgetting it or mistyping it.
When I started in Computer Science, Object-Orientation—or OO—was the future in the Industry. And Functional Programming—or FP—was the future in Academia. But OO and FP people were talking past each other (when they remained polite). 30 years later, most languages have adopted OO, then FP. A few FP, then OO. But OO and FP people still talk past each other. I was raised in academia and made career in the industry, and always wanted to read a paper that would have reconciled OO and FP. A paper that would have explained what OO is and why it matters. Well, you know what they say? If there's a paper you'd like the read that no one else is writing, the only way to read it is to first write it yourself. ==> 1 min. To be cut in half.
When I started in Computer Science, Object-Orientation—or OO—was the future in the Industry. And Functional Programming—or FP—was the future in Academia. But OO and FP people were talking past each other (when they remained polite). 30 years later, most languages have adopted OO, then FP. A few FP, then OO. But OO and FP people still talk past each other. I was raised in academia and made career in the industry, and always wanted to read a paper that would have reconciled OO and FP. A paper that would have explained what OO is and why it matters. Well, you know what they say? If there's a paper you'd like the read that no one else is writing, the only way to read it is to first write it yourself. ==> 1 min. To be cut in half.
I grew up in academia where Functional Programming, FP, reigns supreme. And I made career in the industry, where Object Orientation, OO, is The Thing. For 30 years, I've wanted an explanation, in the terms of FP, of what OO is and why it matters. But OO and FP people have always talked right past each other. Yet one day I discovered... Nix. And that's how I found the explanation, that I will now share with you.
TODO: include (in appendix?) an example of the exponential explosion when you fail to conflate instance and prototype.
TODO: include (in appendix?) an example of the maintenance mess when you fail to use multiple inheritance.
TODO: define multiple inheritance purely on top of mixin inheritance wherein you define a definer prototype (the meta object) from which the actual object is extracted by calling a method.
TODO: add more structure to the presentation, table of contents, etc. mileposts / sections.
have a finishing slide or set of slides wrap-up / takeaways
more practice
mention message-passing vs methods / multimethods
PꚘF, pꚙf
------>8------>8------>8------>8------>8------>8------>8------>8------>8------
We elucidate the essence of Object-Oriented Programming (OOP)
using a constructive approach: we identify a minimal basis of concepts with which to synthesize existing and potential object systems. We reduce them to constructions atop the pure untyped lambda calculus, thereby obtaining both denotational semantics and effective implementation.
We start from the simplest recognizable model of prototype-based OOP, so simple it arguably does not even have “objects” as such. We build further models of increasing sophistication, reproducing a growing subset of features found in past object systems, including original combinations.
We discuss how to deal with issues like types, modularity, classes, mutation—and associated tradeoffs. We use Scheme to illustrate our approach.
------>8------>8------>8------>8------>8------>8------>8------>8------>8------
This talk is a reprise of a paper and talk given at the Scheme Workshop 2021 (in a work done with Alex Knauth and Nada Amin)—with new answers to a couple of then-open questions. The code can be presented in any language with lambdas, and we'll be glad to use whichever language the audience is most familiar with (please advise). Lazy evaluation is slightly preferred (or else side-effects to emulate it). However, subtyping is required for non-trivial types (type discussion included in the talk). The ideas go back to the 1970s and their formalization to the early 1990s, but some of the developments are recent and the point of view on putting it all together is fresh.
------>8------>8------>8------>8------>8------>8------>8------>8------>8------
type Lens s t a b = forall f. Functor f => (a -> f b) -> (s -> f t)
lens: Lens super self inherited field | self <: super | field <: inherited
type Mixin self super = self <: super => self -> super -> self
fix :: Mixin self super -> super -> self
fix mixin base = let self = mixin self base in self
mix :: Mixin self super -> Mixin super sup2 -> Mixin self sup2
mix child parent self super = child self (parent self super)
methodOverride :: (self <: super, method <: inherited) => Lens super self inherited method -> (self inherited -> method) -> Mixin self super
methodOverride lens body self super = lens (body self) super
lensedMixin :: (self <: super, method <: inherited) => Lens super self inherited method -> Mixin method inherited -> Mixin self super
lensedMixin lens mixin self super = over lens (mixin (get lens self)) super
------>8------>8------>8------>8------>8------>8------>8------>8------>8------
;; ------>8------>8------>8------>8------>8------>8------>8------>8------>8------
#| Different variant of prototypes, wherein self is lazy.
(define (fix^ p t) (define s (p (delay s) t)) s)
(define (((slot^ k v^) s^ t) m) (if (eq? m k) (force v) (t k)))
(define (((slotGen^ k f) s^ t) m) (if (eq? m k) (f s^ (delay (t k))) (t k)))
|#
($section "Conclusion: OO is FP"
$plan-slide
($slide "Pure Functional Prototypes are Cool"
@Li{Great for Theory}
@Li{Great for Practice}
@Li{Yields Great Insights}
@Li{Incremental, Modular, Concise code})
https://docs.racket-lang.org/scribble/extra-style.html
https://www.quora.com/What-is-the-Chronology-of-Early-OOP-see-self-answer-for-details/answer/Alan-Kay-11?__filter__=all&__nsrc__=notif_page&__sncid__=52254481076&__snid3__=70031974587
- single vs multiple vs mixin inheritance
- prototypes vs classes
- calling super methods / call-next-method / etc.
- method conflict, method resolution
- class precedence lists
- method combinations
- user-defined method combinations
- no-such-message handlers
- multimethods
- predicate classes
- default values
- instance initialization protocols
- callable instances, instances as array, etc.
- whether to tracking all your instances
- inheritance not being the same as subtyping due to e.g. binary methods or recursive class definitions.
- pure vs stateful objects
- “static” methods, “virtual” methods, etc.
- various visibility and scoping mechanisms.
- Class OO as typelevel metaprogramming
- runtime reflection
- meta-object protocols
- runtime meta-objects
- OO and FP being friends not enemies
- The relationship between classes and typeclasses
- friend classes, mutually recursive classes, orphan typeclasses, etc.
- access paths / accessors / lenses for safe access to internal state
- instance update upon class redefinition
- instance update upon class change—and the even much more powerful smalltalk become
- interactive systems vs programming languages
See the bibliography of the Allen 2011 paper for more on multimethods, multiple inheritance.
Okasaki Pure Functional Data Structures... in the context of how functional style without OO requires you to rewrite everything for every variant of a data structure, rather than only the difference from known templates you inherit from.
There is no OO whatsoever at runtime in C++, only plain procedural imperative stateful programming.
There is no mutable state whatsoever at compile-time in C++, just a pure functional lazy language with Prototype OO—the C++ template language.
Class OO is a lie. There is no such thing.
All OO is Prototype OO. Some of it is used to metaprogram types and procedures on those types.
Usually in an extremely restricted metalanguage, but not always.
Cook's thesis only has a short section on multiple inheritance. His formalism allows him to see issues that most people fail to conceive, yet his two proposed solutions are shallow and bad, despite many actual working systems having existed for a decade already at that time.
His first model has wrappers process a tuple of the direct parent's results. This can account the effects of unknown indirect parents exponentially many times, so wrappers must be CRDTs (which he fails to notice). Hence no one uses it.
His second "strict" model just punts on "conflicts", i.e. cases where multiple parents define distinct methods. That's the least useful non-useless CRDT, and initial solution used by ThingLab, Smalltalk (and MESA?). But by 1989, Flavors and LOOPS did much better.
The solution used by (almost?) everyone since before his thesis is to linearize the inheritance DAG; wrappers are the same as for single- and mixin- inheritance; works with any CRDT without users having to do it the hard way; hence method combinations, standard or not.
I don't want to dunk too much on Cook in particular: Most academic treatment of multiple inheritance is disappointing, especially by prestigious PL professors, who seem never to have used it nor studied how and why OO programmers actually use it.
Makes sense since the point of OO (a.k.a. intralingual incremental modularity) is to build larger systems than fit in anyone's head, what more that evolve with time, whereas academics specialize in short writings about ideas they can fully explain, with no space for evolution.
There are happy exceptions of course, usually works initiated in industrial research labs rather than universities (SELF (and after it CECIL), Fortress (whose team includes Guy Steele who worked on Common Lisp). Because industrial labs build long-term systems, not just papers.
Note that it's not a matter of bad people, but of bad incentives: universities do not reward system building, and those systems that are built nonetheless (e.g. Racket) are mostly made of many small experimental contributions with few resources to turn them into a coherent whole.
Of course, the industry has its own host of bad incentives. They notably explain a consistent neglect of FP and formal methods, except in a few companies that recruit at the high end. And then don't get me started about Applications and "IP". But that's for another thread.
CRDT: I'm using the modern name—just a fancy way of saying that whichever way you encode your "wrapper" results must intrinsically avoid duplicating effects… which comes for free if you linearize your ancestors and apply a plain old wrapper (or one from your method combination).