PageRenderTime 29ms CodeModel.GetById 11ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 1ms

Plain Text | 379 lines | 300 code | 79 blank | 0 comment | 0 complexity | 2e5258bc81cca0d22c868e2724daca49 MD5 | raw file
  1=== TODO ===
  4Conceptual issues:
  7Meta-circular AmbientTalk:
 10Goal: write as much as possible AT/2 code in AT/2 itself. A lot of NAT* classes, especially AG* classes
 11can be written in AT/2 itself. Most NAT* classes are plain AT/2 objects with a custom mirror that overrides
 12only a few operations.
 14The only problem is that this makes the implementation much slower as a lot of NAT classes will then become
 15NATMirages. We should make our implementation of NATMirage as efficient as possible. We need some kind of
 16'partial behavioural reflection' in order to pay only for the reflection that we need.
 17This means that as much meta_* methods should remain hard-wired even for certain mirages, because they are not overridden
 18by the mirror.
 20For those NAT classes that need access to native Java objects, we might be able to use the Symbiosis.
 21As such we may be able to confine the AT/2 kernel to only a handful of classes (NATNil, NATObject, JavaObject)
 25base_isXXX and base_asXXX methods must become meta_isXXX and meta_asXXX
 26I.e. they should be moved to the mirror, in order to minimize the number of base-level methods of normal objects.
 28At the base-level meta_isXXX and meta_asXXX should be exposed as natives in the
 29root: def isNumber(x) { (reflect: x).isNumber() }
 31NATObject implementation should not implement all meta_asXXX methods by creating a coercer by default.
 32Should only create a coercer if the object explicitly declares itself to be a number, mirror etc. via a stripe test or something?
 37We need to augment the standard AT/2 library with a namespace full of prototypes.
 38E.g. especially exception prototypes such that native exceptions can be raised from within the language.
 41The Standard Library:
 44Written entirely in AmbientTalk itself. Accessible via ''
 45We need:
 47- at.init: an initialization file with useful default semantics for most things currently in the OBJLexicalRoot?
 48Should also contain isNumber(x), isTable(x) , ... natives.
 49- at.collections: a small collections library (binary tree, hashtable, vector, list, queue, set) implementing
 50a uniform interface (iteration, collection, filtering, insertion, deletion, lookup)
 51- at.unit: a unit testing framework along the lines of JUnit, the unit testing framework of Ruby etc:
 52make a sub-object of the TestCase object and implement some methods whose name starts with test*. Running the program should run the tests.
 53- at.mirrors: prototypical mirrors on all kinds of language values?
 54- at.exceptions: prototype exception objects for native exception types that can be newed and raised.
 56Identity and equality:
 59Next to the == operator, which defines equality through identity, we should introduce
 60an operator = which defines structural equality (cfr. eq? versus equal? in Scheme).
 61e.g. [2,3] is not == to another [2,3] table, but [2,3] = [2,3]
 63== is already implemented and falls back on Java's equals method, which in turn falls back on Java's == operator.
 64Structural equality implies deep equality.
 65For 'leaf' objects like symbols, numbers, etc., deep equality is identity.
 66For 'compound' objects like tables, all elements of the tables must be deep equal.
 68The same goes for objects. Objects can be considered as maps from symbols to values, so
 69two objects are equal if they map the same symbols to equal values. The problem here
 70is that performing deep equality like this is both
 71A) very costly
 72B) dangerous because it is a graph-traversal
 73(have to keep track of already visited objects? even for tables, consider: t[1] := t)
 75Another kind of comparison between objects that could be useful is 'substitutability'
 76For example, obj1 '<:' obj2 if they have the same field names and method selectors
 77(i.e. the 'range' of their conceptual 'map' is similar) such that obj2 can safely
 78'replace' obj1.
 80One can then define:
 81boolean NATObject.base__opltx__opcol_(ATObject other) {
 82  foreach field | method selector of this {
 83    if (other.meta_respondsTo(selector) {
 84       continue;
 85    } else {
 86       return false;
 87    }
 88  }
 89  return dynamicParent_.base__opeql_(other.meta_getDynamicParent());
 92Note that structural equivalence of objects is defined in terms of their public fields/methods
 93+ those of their dynamic parents. Lexical parents are not taken into consideration, as they are
 94considered 'private' to the object.
 99- First-class constructors: or should wrap a JavaConstructor class,
100such that casting works for constructors as well.
101=> Implementation can be sped up a bit using caching. Currently, only methods are cached, could also
102cache Constructors if we introduce a JavaConstructor class? This could also be used to solve problem of
103casting for constructors. E.g. if returns a JavaConstructor which is a special kind of JavaClosure,
104then we can do, Type2)(a, b)
106- Solve overloading by being able to annotate a method invocation => future work?
108- methods whose selector contains '_' in Java: we should allow _ in AT identifiers.
109Only disadvantage is that Java identifiers with '_' will be displayed using ':' in AT.
110However, an AT selector will not undergo such conversions when remaining within AT.
111Can be made even cleaner if the _ -> : conversion is only performed for downSelector,
112and for symbiosis introduce another method named 'javaToATSelector'
114Keywords and Optional/Varargs:
117Remember that we wanted some kind of keyworded message sends that
118were matched against a kind of 'regular expression' for stuff like
119try-catch and when-catch, such that e.g. the following invocations
120all trigger the same language construct:
121when: f becomes: { |v| ... }
122when: f becomes: { |v| ... } due: 1000
123when: f becomes: { |v| ... } catch: { #Exc1 |e| ... } catch: { #Exc2 |e| ... }
125This could be done using optional and vararg parameters with keywords:
126def when: future becomes: block due: time := default catch: @blocks {
127  ...
129This method actually has a selector when:becomes:[due:](catch:)*
130and regexp matching could be used to match invocation with definition.
132We really need this kind of semantics if we want to make optional
133arguments and varargs work for keywords.
135Meta-send (send-to-mirror):
138Consider introducing another message passing operator (.?) which sends a message
139to an object's mirror rather than to the object itself.
141obj.?msg(args) == (reflect: obj).msg(args).base
143The advantage is that the meta-variant does not require the creation of temporary mirrors.
145The reflect: variant is much more expensive; it requires creating a mirror on obj
146and a mirror on the result, which are usually immediately discarded.
148Next to efficiency considerations, the meta-send is also a more concise way
149of sending messages to mirrors.
154Annotations or stripes or types are necessary for multiple reasons:
155- For object classification (e.g. allow objects to categorize themselves
156into domain-specific categories, or tell to the interpreter that they represent
157a table, a function, a number, etc. -> for better coercion)
158- For symbiosis with Java (e.g. objects should tell AmbientTalk which Java
159interfaces they implement)
160- For exception handling (catch only exception objects of a certain kind)
161- For service discovery: need a topic hierarchy to allow interpreter
162to rapidly filter/route discovery events)
164All of these require 'subtyping' semantics
165(i.e. an annotation/stripe/type can have 0 or more supertypes and a subtype is substitutable for a supertype)
167Apart from the above requirements to tag *objects*, sometimes it would be
168interesting to also tag *fields*, *methods*, *message sends* or even *closures*
169Here, these annotations represent declarative directives to the meta-level, which
170e.g. alter field access, method invocation, message dispatch
171- fields: proxy objects that implement get and set
172- methods: proxy objects that implement apply and applyInScope
173- message: proxy implementing sendTo
174- closure: ?
175Especially interesting for introducing proxies (cfr. the 'function transformations' from dPico)
176(custom fields, method wrappers)
177A requirement for these annotations is that they should carry arguments, like:
179e.g. obj<-msg(a,b,c)#delay(1000)
181Such annotations could e.g. be used to solve overloading problems with the Java symbiosis:
182javaobject.method(a#type1, b#type2, c#type3)
183or one could pass an object tagged with a correct type
185It would be nice to integrate annotations consistently into the language and be able to
186use them both as directives to the meta-level *and* as types for objects.
188Transform annotations into message sends?
189def #foo x := 5 => def x := foo().decorateField(`x, 5) // of decorateField(<field object>)
190=> value? should be a custom field object!
191def #bar(1) meth(args) { ... } => def meth := bar(1).decorateMethod(<method object>) => method
192obj.msg(args)#foo => obj <+ foo().decorateMessage(<msg object>) => message
193{ #foo |args| body } => foo().decorateClosure({ |args| body }) => closure
195object: #foo { ... } => decorateParameter
196object: { #foo ... } => decorateClosure
197object: { ... }#foo => decorateParameter
199Asynchronous function invocation:
202Introduce the shorthand syntax fun<-(a,b,c) to denote fun<-apply([a,b,c])
203such that remote references to closures are more easily invokable.
205Isolates and Import:
208Import internally makes use of ClosureMethod objects to implement the delegate methods
209that are imported into the host. These objects are currently not isolates, so when an isolate
210uses import, it will probably lose its delegate methods when being passed by copy.
212Perhaps the methods should be actually copied when being imported into an isolate?
214Import should also become dedicated syntax.
216Far References:
219Proper implementation for ==, new and init on far refs
220(== should work, new and init can throw exception)
225Intercessive mirrors are always tied to a particular 'base' object.
226The default intercessive mirror is named 'mirrorroot' and is an object
227that understands all meta_* operations, implementing them using default semantics.
229It can be thought of as being defined as follows:
231def mirrorroot := object: {
232  def base := object: { nil } mirroredBy: self // base of the mirror root is an empty mirage
233  def init(b) {
234    base := b
235  }
236  def invoke(@args) { <default native invocation behaviour> }
237  def select(@args) { <default native selection behaviour> }
238  ...
239} stripedWith: [ Mirror ]
241This object can then simply be extended / composed by other objects to deviate from the default semantics.
242Hence, 'mirrors' are simply objects with the same interface as this mirrorroot object: they should be
243able to respond to all meta_* messages and have a 'base' field.
245When a mirror is cloned, its base field is *shared* with the clone. However, the base object will still be
246tied to the original mirror. Therefore, the default clone: operator is implemented as follows:
248def clone: obj {
249  if: (is: obj stripedWith: Mirror) then: {
250    reflect: (clone: obj.base)
251  } else: {
252    (reflect: obj).clone()
253  }
256In other words: when a mirror is cloned, the mirage is cloned instead (recursively) and a mirror on the new mirage is returned.
257Cloning mirages also clones the corresponding mirror - and re-initializes the cloned mirror's mirage to the cloned mirage, so there
258the bidirectional link between mirror and mirage is not broken.
260There is another way to assign mirrors to mirages, by means of the object:mirroredBy: primitive:
262def mirage := object: {
263  ...
264} mirroredBy: aMirror
266Internally, this operation is translated into<uninitialized mirage>)
268which is:
269aMirror.meta_clone().invoke("init", [<uninitialized mirage>])
271For regular mirrors, which are IS-A children of the mirrorroot, this leads to the invocation of 'init' on aMirror,
272and eventually also to the invocation of 'init' on OBJMirrorRoot. This init implementation sets the base field to point
273to the new mirage, and initializes the 'mirror' field of the mirage to the dynamic receiver of 'init'.
274From that point on, the mirage is considered initialized and can be used.
275An 'uninitialized' mirage is one whose mirror is assigned to 'nil'.
276When explicitly making a new instance of a mirror, OBJMirrorRoot checks whether the object
277is receives in its init method is indeed a NATMirage, and whether its mirror is != nil.
280Implementation issues:
283Meta-circular AmbientTalk:
286Related to a proper meta-circular implementation of AmbientTalk is to allow as much custom AT objects in
287place of implementation-level objects. That is, we should remove as much .asNative...() casts from the code
288as possible. Each such cast precludes use of custom AT objects. Sometimes, the casting is inevitable (e.g. 1+2),
289sometimes, it is not. E.g.: rather than casting an ATObject to a NATTable and looping over its elements_, one could write:
290if (obj instanceof NATTable) {
291  // perform immediate native behaviour
292  // loop over ((NATTable) obj).elements_ with for loop
293} else {
294  obj.base_asTable().base_each(new NativeClosure(this) {
295    // perform native behaviour inside of a native closure's apply method
296  });
299Native method sanity check:
301- zorgen dat alle AT interfaces alleen nog base_ / meta_ methoden hebben die InterpreterException throwen
302en die alleen AT args of convertible args returnen / nemen
304NATText meta_print() throws InterpreterException;
305alle isXXX() methods => returnen boolean
306ATObject base_object_mirroredBy_(ATClosure code, NATIntercessiveMirror mirror) throws InterpreterException;
307en varianten: nemen expliciet een NATIntercessiveMirror ipv een ATMirror
309Static Variables:
312We must ensure the integrity of all static variables in the AT/2 implementation with respect
313to race conditions! Static variables are shared by multiple actors. This means that they
314should definitely be final + that the datastructure they point at is immutable. If not,
315e.g. the string pool for symbols, access should be synchronized!
318Native Method Refactoring:
321Goal is to get rid of most methods from the JavaInterfaceAdaptor and Reflection classes,
322and to make native AmbientTalk methods accessible without having to perform a reflective
323Java method invocation.
325Primary goal: to remove all reflective invocations of base_ methods from the implementation.
326Instead, we want to represent native methods more closely as AmbientTalk methods.
327Like this:
328- we don't need to convert AT selectors into Java identifiers,
329- we can reuse AT's parameter passing mechanism for native methods (e.g. varargs)
330- we don't have to perform a reflective Java method invocation.
332To change:
333- every NAT class should have a static final HashTable that represents
334the method dictionary of all objects of that NAT type.
336- at startup time, this HashTable should be filled with the native
337methods of that NAT type. Native methods are represented as anonymous
338classes that implement a kind of native_apply method, which is parameterized
340- the Java 'this' (because native methods are static and shared by all
341instances, they are selfless, which should be of the proper NAT type
342[- the AmbientTalk 'self' (necessary if a native method needs late binding))]
343- a call frame that binds formals to actuals
345- meta_invoke on native objects (e.g. NATNil) should query the class-level
346hashtable for the appropriate native method corresponding to the selector,
347and apply the native method. Alternatively, meta_invoke could become a large
348switch statement, but this is harder to maintain + would lead to code duplication
349in between meta_invoke, meta_delegate, meta_select.
351Consider for example the method '+' in NatNumber:
352class NATNumber extends NATNumeric {
353  private final int javaValue_;
354  private static final HashTable _NATNumber_METHODS_ = new HashTable();
355  static {
356    _NATNumber_METHODS_.put(AGSymbol.alloc("+"), new NativeMethod("+",new ATObject[]{AGSymbol("other")}) {
357      public ATObject native_apply(NATNumber ths, ATObject slf, NATCallframe args) {
358        ATObject other = args.meta_select(AGSymbol.alloc("other")); // alternatively, do
359        return ths.base_plus(other.meta_asNumber());
360      }
361    }
362  }
363  public ATNumber base_plus(ATNumber other) { ... }
366Alternatively, to speed up lookup of native methods,
367each native method's selector could be assigned a number, and
368lookup can occur in O(1) by storing the methods in an array where
369the indices correspond to the selector's number.
372Introspective mirrors need to be adapted similarly:
373They should implement native methods with selectors "invoke", "select", etc.
375new NativeMethod("invoke",new ATObject[] { AGSymbol("selector"), AGSymbol("args") }) {
376  public ATObject native_apply(NATMirror m, ATObject self, NATCallframe args) {
377     return up(m.base.meta_invoke("selector").meta_asSelector(),"args").meta_asTable()));
378  }