1# Objects
2
3See Records.md for a counter-proposal.
4
5An object consists of a map from names to values (these are the fields),
6plus an ordered sequence of values (these are the elements).
7
8A simple object, with 2 fields (x and y) and 3 elements (1,2,3):
9```
10obj = {x=true, y="blue", 1, 2, 3};
11```
12
13Objects are a generalization of lists. An object is like a list
14with the addition of metadata (name/value pairs).
15When list operations are applied to an object, they act on the elements.
16```
17len(obj) => 3
18obj@0 => 1
19elements(obj) => [1,2,3]
20[for (elem=obj) elem] => [1,2,3]
21```
22
23Fields can be referenced using dot notation.
24Here are the field operations:
25```
26obj.x => true
27obj.("y") => "blue"
28defined(obj.x) => true
29defined(obj.("z")) => false
30fields(obj) => ["x", "y"]
31[for (key=fields(obj)) obj.(key)] => [true,"blue"]
32```
33
34There are two kinds of object, simple and scoped.
35They have different constructor syntaxes and internal representations,
36with different performance characteristics, but the two kinds are
37interchangeable, and there is only one object type.
38
39## Simple Objects
40
41A simple object constructor is a set of zero or more field definitions
42and element expressions separated by commas.
43The order of the definitions doesn't matter.
44```
45{ a = 1, b = 2 }
46```
47There is no scope introduced inside the constructor.
48
49Simple objects are used to represent sets of model parameters,
50and for the requirements object in the shape3d protocol.
51
52The representation is probably a binary tree with refcounted nodes,
53plus a list.
54
55## Scoped Objects
56
57Scoped objects extend simple objects with dependencies and parameter fields.
58You can customize a scoped object using function call syntax, specifying
59new parameter values as arguments, and a new object will be constructed
60(with all dependent fields and elements updated with the new parameter values).
61The use cases are parameterized shapes and libraries.
62
63A scoped object constructor introduces a lexical scope, so that
64field definitions can refer to each other, which creates dependencies.
65The scope of each definition is the entire constructor, and the order
66of definitions doesn't matter.
67There are two kinds:
68* a script file, which is a sequence of statements.
69* `{`*script*`}`, which contains one or more statements.
70
71For example, `{x=1; y=x+1;}` is a scoped object where `y` depends on `x`.
72
73If a definition is prefixed with the `param` keyword, then it is a
74parameter field.
75
76Statements are terminated by semicolons.
77A statement is:
78* a definition, x=0; or f(x)=x+1; with optional `param` prefix.
79* `use` statements (library support)
80* `include` statements (object concatenation; inheritance semantics)
81* side effects: assert and echo
82* expressions and generators, like in list constructors
83
84`include x;` requires `x` to be a list or object.
85* It interpolates the elements of `x` into the base object's element list
86  at the point where the `include` statement occurs.
87* If `x` is an object, then the effect is to concatenate the fields onto
88  the base object, in the manner of `concat[base,x]`.
89  If an object literal contains include statements, then the field values
90  of the resulting object are determined by first processing all of the
91  definitions, then processing the first include statement and concatenating
92  those fields (they will override definitions from the first phase),
93  then processing the second include, and so on.
94* In the initial Curv implementation, we may restrict `x` to be only
95  a list, or to be only a simple object (no dependencies or parameters).
96
97The representation is a slot vector and a dictionary.
98* The dictionary is shared by all clones of the object, which speeds up
99  and reduces the memory cost of customization.
100* The dictionary contains some representation of the parse or meaning tree
101  for each definition, used for implementing inheritance (include and overlay).
102
103The order of definitions doesn't matter (as far as dependencies go);
104the scope of each definition is the entire script.
105There are *no* restrictions on recursive definitions, like Haskell.
106That means `x=x;` is legal and `x` is an infinite loop.
107
108The order of actions is relevant: list items are ordered, side
109effects are ordered. The side effects actions are evaluated when the
110object constructor is evaluated. The other things are
111
112A script file denotes a scoped object, but in this special case,
113top level definitions are lazy, stored as parse trees until first
114reference, when they are JIT compiled.
115
116Idiom: a function constructs an object, the function parameters have
117the same names as corresponding object parameters. How is this possible?
118```
119(a) f(a) let(outer_a=a) {a=outer_a;} // a clumsy idiom, proof of concept
120(b) f(a) {a=outer.a;} // outer is a keyword
121(c) f(a) {a=a} // simple objects don't introduce a scope
122```
123
124Parameterized objects. One or more definitions can be prefixed with `param`,
125which makes them into parameters. An object can be cloned or customized
126using function call notation, replacing parameter values with new values.
127* This is relevant to the GUI customizer: only parameters can be tweaked
128* Parameters are compiled differently.
129  They are always allocated to slots, even if they have a static value.
130  This means the customizer gui can run without recompiling the script.
131* The order of parameter definitions matters for passing them as positional
132  arguments during customization.
133
134## Object Concatenation
135
136An object is a set of name/value pairs, plus a sequence of values.
137Objects are a generalization of lists. This is reflected in the `concat`
138operator, which concatenates objects, lists, or a mixture of both.
139
140semantics of `concat[a,b]`:
141* It's associative, but not commutative.
142* `[]` is the identity element.
143* `concat[obj,list]`: `list` is appended to the end of `obj`s element sequence.
144* `concat[list,obj]`: `list` is prepended to the beginning
145  of `obj`s element sequence.
146
147semantics of object concatenation. `concat[base,ext]`:
148* The resulting object has the union of the fields in base and ext,
149  with the field values of ext taking precedence and overriding the base.
150* The elements of the resulting object
151  is concat[elements(base), elements(ext)].
152* `{}` is an identity element for object concatenation.
153
154In the initial Curv implementation, object concatenation will
155always produce a simple object. Possibly we abort if argument object has
156dependencies and/or parameters? (This would be independent of the syntax
157of the original constructor.) This implies a predicate to test if an object
158has dependencies or parameters. It also violates the principle that a
159scoped object is interchangeable with a simple object (dependencies and
160parameters are ignored in contexts where they are irrelevant).
161Maybe there is a different, more expensive operation for composing
162scoped objects, which reduces to `concat` in the simple case.
163Although that introduces a picky, fine grained distinction that
164for simplicity should be avoided.
165
166Later, we may try a more sophisticated design that preserves
167the dependencies and parameters of scoped objects:
168* If the base has parameter fields, then the result has the same parameter
169  fields in the same order. The result can be customized using the same
170  argument lists that work for the base.
171* If a list and a scoped object are concatenated, the resulting object
172  is scoped and preserves the input object's dependencies and parameters.
173* If base and ext are simple, the result is simple.
174  This operation is cheap: maybe a few tree nodes are copied, and linear
175  update requires no copies.
176  Concatenation with scoped objects is more expensive.
177* If base is simple and ext is scoped, the result is scoped.
178  The result has the same dependencies and parameter fields as ext.
179  (Necessary because `{}` is an identity element.)
180* If the base is scoped, then the result is scoped.
181  Dependencies within ext are copied into the result.
182  The parameter status of fields within ext are ignored if those fields
183  override existing fields within base. However, ext can add new parameter
184  fields to the end of base's parameter list.
185
186What happens when you concatenate to or from a shape object?
187* Shape objects are internally branded by the shape3d operator,
188  and are members of the `is_shape` predicate.
189* Let's consider how user-defined object branding works.
190  You define a predicate, eg:
191        ```
192        is_polytope(x) = if (defined x.polytope) x.polytope else false;
193        ```
194  then you define `polytope=true` to brand your object.
195  This style of branding can be inherited from either the `base` or `ext`
196  arguments of object concatenation, as long as its not overridden to false
197  by `ext`.
198* By analogy, `concat[base,ext]` returns a shape if either `base` or `ext`
199  is a shape. `shape3d` is run on the result to verify that it's a valid
200  shape, and that verification may fail.
201* This is consistent with `{}` and `[]` being identity elements.
202
203For example, `concat[cube(10),{meta=42}]` shows how to add metadata
204to a shape. Of course, you need to make sure not to interfere with
205a field needed for the shape's correct operation.
206An alternative is `concat[{meta=10},cube(10)]` which guarantees not to
207override an existing field, only to add a new field if it wasn't present.
208
209## Concat and Include
210`include` is supposed to be the generator form of `concat`; they work on
211lists and objects in a consistent way.
212
213Is this true? Is the design consistent? Does it uphold Reynold's Law?
214Let's consider some algebraic identities which should hold.
215* `[a, include b]` === `concat[[a], b]`
216  * true for lists a and b.
217  * In order for this to be true for object 'b', upholding Reynold's Law,
218    the list constructor would have to return an object. Which is undesirable.
219
220An alternative is to split the design into two families of operations.
221* `concat` and `each` are list operations. An object is projected onto
222  its list of elements when used with these operations.
223* `extend` and `include` are object operations. A list is extended to
224  a simple object with elements but no fields when used with these operations.
225
226`extend(base, ext)` is maybe not a monoid. It treats the arguments differently.
227
228## Customization:
229* Invoke an object like a function, this creates a clone of the original
230  with each argument replacing an existing field that is declared as `param`.
231  This is only useful with heavy objects, and is designed to be cheap.
232* Simple object literals produce objects with no parameters.
233  Any object can be invoked as obj(), which just returns obj.
234
235## Libraries
236* `use obj;`
237
238optional: `only(id1,id2,...)obj` and `except(id1,id2,...)obj`
239
240## Inheritance
241Object concatenation for scoped objects is kind of like OOP inheritance.
242It's the most complex and heavy weight part of the OpenSCAD2 object design.
243It's a low priority; I can demo Curv without it.
244
245I see no strong need for OpenSCAD mixins.
246I think you can implement them like this:
247```
248my_mixin(base) = concat[base, {
249  assert(defined base.n && is_num(base.n));
250  x = base.n + 1;
251}];
252```
253This is certainly good enough.
254
255## Old Design Process
256Modifying an object: fast runtime operations.
257These operations treat an object as a map of independent key-value pairs.
258You can update one of these pairs without affecting the others.
259Source code dependencies between definiens don't matter here.
260You can also add new key-value pairs.
261* `obj(arg1,arg2,...)` -- "customization". Fields defined as 'param'
262  can be overridden. It's fast: we allocate a new slot array but share
263  the original dictionary that maps names to slot ids. The new values
264  are stored in slots.
265* `obj1 <+ obj2` or something. Override arbitrary fields, add new fields.
266* The OOP and inheritance-oriented object representation might not make
267  sense. Instead, maybe use a std::map, copy on write, mutate if refcount==1.
268* There's a temptation to create a different map type, but I don't want to
269  do that. If I have two map representations, how do I choose which to use?
270  Maybe it's a syntax distinction without a type distinction,
271  like cons() vs concat() for lists. {a=1,b=2} creates the std::map
272  representation, where linear overlays are cheap, vs the `;` syntax.
273  But they are the same type, semantically interchangeable.
274* Haskell Data.Map is a binary tree, similar to std::map. On an insertion,
275  only a few nodes need to be copied. Clojure uses hash array-mapped trie.
276* With the {,} syntax, there are no dependencies between fields,
277  which are important to the 'inheritance' operators.
278  Also no params, no actions.
279  Eg, {x=1,a=x} means:
280  * No new scope is introduced, the x in a=x references an external x.
281    That would also mean no recursive definitions, no thunks.
282  * Or, we use recursive scoping, but the dependencies between definiens
283    are thrown away when the map is constructed. But, that creates an
284    implementation difficulty for recursive definitions, due to rthunks
285    needing a slot vector.
286* If I have a 'simple' and a 'scoped' object notation,
287  with different internal representions, then
288  what happens if they are combined? Compile time shit happens when heavy
289  objects are combined, so that happens here too.
290
291Extension operations:
292* `obj1 overlay obj2` or `{include obj1; overlay obj2;}`
293* As a change to OpenSCAD2, maybe `{include obj; x=a;}` overlays the
294  new definition of `x`. This is closer to original OpenSCAD semantics,
295  and preserves the order independence of OpenSCAD2?
296  The original design document claims this is confusing, and suggests
297  `include obj1 overlay {x=a};` so it's clear that x=a is an override,
298  and so it's clear which object it is overriding.
299* `include`, `use` and `overlay`.
300* I have plans to compile objects into optimized code, with static definitions
301  treated as compile time constants that can be constant folded, etc.
302  Use `param` to allow run-time customization. This means that `include`,
303  `use` and `overlay` may involve recompiling the amended script.
304* Is there a fast overlay operator that doesn't involve recompiling?
305  I'd want that for shape3d. To be clear, this means different semantics.
306  I'm treating the object as a map containing an independent set of values,
307  and overriding values in the map doesn't re-evaluate dependent expressions.
308
309## overlay
310How do param definitions and overlays interact?
311* for shape3d::dist::req, I don't care, I won't be using param definitions
312  together with override.
313* If an object has parameters, then an API is established for customizing
314  the object. An object is no longer a simple map. Overlay is more like
315  inheritance and override in an OOP language.
316  * Probably, you don't want to break the API, just extend it in an
317    upward compatible way (as in interface inheritance).
318    `{include obj1; overlay obj2;}` is interface inheritance and override.
319  * Possibly, you intend to do implementation inheritance and establish
320    a new API. But then, you should `use obj1;` and then define all of
321    fields in the new API with reference to obj1. You get to define
322    from scratch which fields are parameters.
323* case analysis:
324  * non-param overlays param, what happens?
325    * Is the non-param/param property overridden?
326      * In the common case, I think you are specifying a new value for
327        an existing parameter without changing the inherited API.
328      * If this makes sense, then wouldn't we also provide a way to
329        deparameterize an inherited parameter without specifying a new value?
330      * Yes, because this lets you make changes to that property in a general
331        way.
332      * Note that customization, obj(x=a), does not change the property.
333    * Does the relative location of the field in the param order change?
334      * ACCEPT: No, because then you can't in general override single parameter
335        values without also changing their order. The order might be
336        an important aspect of the API, and you don't want to break the
337        API just to override a field. (To preserve order you
338        could override all existing parameters, which is a clumsy requirement
339        to avoid breaking an API. Also, you might not know all of the field
340        values, as is the case in a shape3d requirements object.)
341      * REJECT: Yes, because this lets you change parameter order. But changing
342        the parameter order is a big deal, it should require a heavier weight
343        syntax. In this case, it would make sense to restate all of the
344        parameters when establishing a new order.
345  * param overlays non-param, what happens? (ditto.)
346  * new param field: how is it ordered relative to inherited param fields?
347    * New param fields always come after base param fields,
348      to avoid breaking the API.
349    * REJECT: With the action version of `overlay`, the new fields are located
350      based on where `overlay` is located within the object literal
351      relative to the base field definitions.
352
353obj(x=a) is a bit like concat[obj,{x=a}].
354