PageRenderTime 59ms CodeModel.GetById 48ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 0ms

Plain Text | 385 lines | 306 code | 79 blank | 0 comment | 0 complexity | f7330c5eb14458869aefcbe1ea3de679 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)
 26We need to augment the standard AT/2 library with a namespace full of prototypes.
 27E.g. especially exception prototypes such that native exceptions can be raised from within the language.
 30The Standard Library:
 33Written entirely in AmbientTalk itself. Accessible via ''
 34We need:
 36- at.init: an initialization file with useful default semantics for most things currently in the OBJLexicalRoot?
 37Should also contain isNumber(x), isTable(x) , ... natives.
 38- at.collections: a small collections library (binary tree, hashtable, vector, list, queue, set) implementing
 39a uniform interface (iteration, collection, filtering, insertion, deletion, lookup)
 40- at.unit: a unit testing framework along the lines of JUnit, the unit testing framework of Ruby etc:
 41make a sub-object of the TestCase object and implement some methods whose name starts with test*. Running the program should run the tests.
 42- at.mirrors: prototypical mirrors on all kinds of language values?
 43- at.exceptions: prototype exception objects for native exception types that can be newed and raised.
 45Identity and equality:
 48Next to the == operator, which defines equality through identity, we should introduce
 49an operator = which defines structural equality (cfr. eq? versus equal? in Scheme).
 50e.g. [2,3] is not == to another [2,3] table, but [2,3] = [2,3]
 52== is already implemented and falls back on Java's equals method, which in turn falls back on Java's == operator.
 53Structural equality implies deep equality.
 54For 'leaf' objects like symbols, numbers, etc., deep equality is identity.
 55For 'compound' objects like tables, all elements of the tables must be deep equal.
 57The same goes for objects. Objects can be considered as maps from symbols to values, so
 58two objects are equal if they map the same symbols to equal values. The problem here
 59is that performing deep equality like this is both
 60A) very costly
 61B) dangerous because it is a graph-traversal
 62(have to keep track of already visited objects? even for tables, consider: t[1] := t)
 64Another kind of comparison between objects that could be useful is 'substitutability'
 65For example, obj1 '<:' obj2 if they have the same field names and method selectors
 66(i.e. the 'range' of their conceptual 'map' is similar) such that obj2 can safely
 67'replace' obj1.
 69One can then define:
 70boolean NATObject.base__opltx__opcol_(ATObject other) {
 71  foreach field | method selector of this {
 72    if (other.meta_respondsTo(selector) {
 73       continue;
 74    } else {
 75       return false;
 76    }
 77  }
 78  return dynamicParent_.base__opeql_(other.meta_getDynamicParent());
 81Note that structural equivalence of objects is defined in terms of their public fields/methods
 82+ those of their dynamic parents. Lexical parents are not taken into consideration, as they are
 83considered 'private' to the object.
 88- First-class constructors: or should wrap a JavaConstructor class,
 89such that casting works for constructors as well.
 90=> Implementation can be sped up a bit using caching. Currently, only methods are cached, could also
 91cache Constructors if we introduce a JavaConstructor class? This could also be used to solve problem of
 92casting for constructors. E.g. if returns a JavaConstructor which is a special kind of JavaClosure,
 93then we can do, Type2)(a, b)
 95- Solve overloading by being able to annotate a method invocation => future work?
 97- methods whose selector contains '_' in Java: we should allow _ in AT identifiers.
 98Only disadvantage is that Java identifiers with '_' will be displayed using ':' in AT.
 99However, an AT selector will not undergo such conversions when remaining within AT.
100Can be made even cleaner if the _ -> : conversion is only performed for downSelector,
101and for symbiosis introduce another method named 'javaToATSelector'
103Keywords and Optional/Varargs:
106Remember that we wanted some kind of keyworded message sends that
107were matched against a kind of 'regular expression' for stuff like
108try-catch and when-catch, such that e.g. the following invocations
109all trigger the same language construct:
110when: f becomes: { |v| ... }
111when: f becomes: { |v| ... } due: 1000
112when: f becomes: { |v| ... } catch: { #Exc1 |e| ... } catch: { #Exc2 |e| ... }
114This could be done using optional and vararg parameters with keywords:
115def when: future becomes: block due: time := default catch: @blocks {
116  ...
118This method actually has a selector when:becomes:[due:](catch:)*
119and regexp matching could be used to match invocation with definition.
121We really need this kind of semantics if we want to make optional
122arguments and varargs work for keywords.
124Meta-send (send-to-mirror):
127Consider introducing another message passing operator (.?) which sends a message
128to an object's mirror rather than to the object itself.
130obj.?msg(args) == (reflect: obj).msg(args).base
132The advantage is that the meta-variant does not require the creation of temporary mirrors.
134The reflect: variant is much more expensive; it requires creating a mirror on obj
135and a mirror on the result, which are usually immediately discarded.
137Next to efficiency considerations, the meta-send is also a more concise way
138of sending messages to mirrors.
140Update: now that we have message annotations, we could use them to express this
141operation as obj.msg(args)@Meta
143The only problem is that, if such messages are sent *directly* to the mirror,
144this would circumvent the mirror factory (e.g. the use of reflect: to obtain a mirror
145on an object).
150Annotations or stripes or types are necessary for multiple reasons:
151- For object classification (e.g. allow objects to categorize themselves
152into domain-specific categories, or tell to the interpreter that they represent
153a table, a function, a number, etc. -> for better coercion)
154- For symbiosis with Java (e.g. objects should tell AmbientTalk which Java
155interfaces they implement)
156- For exception handling (catch only exception objects of a certain kind)
157- For service discovery: need a topic hierarchy to allow interpreter
158to rapidly filter/route discovery events)
160All of these require 'subtyping' semantics
161(i.e. an annotation/stripe/type can have 0 or more supertypes and a subtype is substitutable for a supertype)
163Apart from the above requirements to tag *objects*, sometimes it would be
164interesting to also tag *fields*, *methods*, *message sends* or even *closures*
165Here, these annotations represent declarative directives to the meta-level, which
166e.g. alter field access, method invocation, message dispatch
167- fields: proxy objects that implement get and set
168- methods: proxy objects that implement apply and applyInScope
169- message: proxy implementing sendTo
170- closure: ?
171Especially interesting for introducing proxies (cfr. the 'function transformations' from dPico)
172(custom fields, method wrappers)
173A requirement for these annotations is that they should carry arguments, like:
175e.g. obj<-msg(a,b,c)#delay(1000)
177Such annotations could e.g. be used to solve overloading problems with the Java symbiosis:
178javaobject.method(a#type1, b#type2, c#type3)
179or one could pass an object tagged with a correct type
181It would be nice to integrate annotations consistently into the language and be able to
182use them both as directives to the meta-level *and* as types for objects.
184Transform annotations into message sends?
185def #foo x := 5 => def x := foo().decorateField(`x, 5) // of decorateField(<field object>)
186=> value? should be a custom field object!
187def #bar(1) meth(args) { ... } => def meth := bar(1).decorateMethod(<method object>) => method
188obj.msg(args)#foo => obj <+ foo().decorateMessage(<msg object>) => message
189{ #foo |args| body } => foo().decorateClosure({ |args| body }) => closure
191object: #foo { ... } => decorateParameter
192object: { #foo ... } => decorateClosure
193object: { ... }#foo => decorateParameter
195Asynchronous function invocation:
198Introduce the shorthand syntax fun<-(a,b,c) to denote fun<-apply([a,b,c])
199such that remote references to closures are more easily invokable.
201Isolates and Import:
204Import internally makes use of ClosureMethod objects to implement the delegate methods
205that are imported into the host. These objects are currently not isolates, so when an isolate
206uses import, it will probably lose its delegate methods when being passed by copy.
208Perhaps the methods should be actually copied when being imported into an isolate?
209The problem with this is that this loses lexical scoping rules:
210a copied variable ref 'x' has a different binding!
215Intercessive mirrors are always tied to a particular 'base' object.
216The default intercessive mirror is named 'mirrorroot' and is an object
217that understands all meta_* operations, implementing them using default semantics.
219It can be thought of as being defined as follows:
221def mirrorroot := object: {
222  def base := object: { nil } mirroredBy: self // base of the mirror root is an empty mirage
223  def init(b) {
224    base := b
225  }
226  def invoke(@args) { <default native invocation behaviour> }
227  def select(@args) { <default native selection behaviour> }
228  ...
229} stripedWith: [ Mirror ]
231This object can then simply be extended / composed by other objects to deviate from the default semantics.
232Hence, 'mirrors' are simply objects with the same interface as this mirrorroot object: they should be
233able to respond to all meta_* messages and have a 'base' field.
235When a mirror is cloned, its base field is *shared* with the clone. However, the base object will still be
236tied to the original mirror. Therefore, the default clone: operator is implemented as follows:
238def clone: obj {
239  if: (is: obj stripedWith: Mirror) then: {
240    reflect: (clone: obj.base)
241  } else: {
242    (reflect: obj).clone()
243  }
246In other words: when a mirror is cloned, the mirage is cloned instead (recursively) and a mirror on the new mirage is returned.
247Cloning mirages also clones the corresponding mirror - and re-initializes the cloned mirror's mirage to the cloned mirage, so there
248the bidirectional link between mirror and mirage is not broken.
250There is another way to assign mirrors to mirages, by means of the object:mirroredBy: primitive:
252def mirage := object: {
253  ...
254} mirroredBy: aMirror
256Internally, this operation is translated into<uninitialized mirage>)
258which is:
259aMirror.meta_clone().invoke("init", [<uninitialized mirage>])
261For regular mirrors, which are IS-A children of the mirrorroot, this leads to the invocation of 'init' on aMirror,
262and eventually also to the invocation of 'init' on OBJMirrorRoot. This init implementation sets the base field to point
263to the new mirage, and initializes the 'mirror' field of the mirage to the dynamic receiver of 'init'.
264From that point on, the mirage is considered initialized and can be used.
265An 'uninitialized' mirage is one whose mirror is assigned to 'nil'.
266When explicitly making a new instance of a mirror, OBJMirrorRoot checks whether the object
267is receives in its init method is indeed a NATMirage, and whether its mirror is != nil.
269The only problem currently with object:mirroredBy: is that the mirror is *implicitly* instantiated,
270which also means that the arguments to new are always [base]. This is annoying if the mirror should be
271parameterized with other properties as well. Perhaps introduce
272object: { ... } mirroredBy: aMirror initialize: [ extra args ]
274Lexical Scope vs External Definitions:
277When performing e.g. def o.x := 5
278and 'x' was already lexically visible in the scope of 'o', should this
279definition become visible, or should all references to 'x' in the scope of o
280remain bound to the lexically visible x?
282Currently, the external definition will 'shadow' the lexically visible 'x'.
283Using lexical addressing might solve such issues.
286Implementation issues:
289Meta-circular AmbientTalk:
292Related to a proper meta-circular implementation of AmbientTalk is to allow as much custom AT objects in
293place of implementation-level objects. That is, we should remove as much .asNative...() casts from the code
294as possible. Each such cast precludes use of custom AT objects. Sometimes, the casting is inevitable (e.g. 1+2),
295sometimes, it is not. E.g.: rather than casting an ATObject to a NATTable and looping over its elements_, one could write:
296if (obj instanceof NATTable) {
297  // perform immediate native behaviour
298  // loop over ((NATTable) obj).elements_ with for loop
299} else {
300  obj.base_asTable().base_each(new NativeClosure(this) {
301    // perform native behaviour inside of a native closure's apply method
302  });
305Native method sanity check:
307- zorgen dat alle AT interfaces alleen nog base_ / meta_ methoden hebben die InterpreterException throwen
308en die alleen AT args of convertible args returnen / nemen
310NATText meta_print() throws InterpreterException;
311alle isXXX() methods => returnen boolean
312ATObject base_object_mirroredBy_(ATClosure code, NATIntercessiveMirror mirror) throws InterpreterException;
313en varianten: nemen expliciet een NATIntercessiveMirror ipv een ATMirror
315Static Variables:
318We must ensure the integrity of all static variables in the AT/2 implementation with respect
319to race conditions! Static variables are shared by multiple actors. This means that they
320should definitely be final + that the datastructure they point at is immutable. If not,
321e.g. the string pool for symbols, access should be synchronized!
324Native Method Refactoring:
327Goal is to get rid of most methods from the JavaInterfaceAdaptor and Reflection classes,
328and to make native AmbientTalk methods accessible without having to perform a reflective
329Java method invocation.
331Primary goal: to remove all reflective invocations of base_ methods from the implementation.
332Instead, we want to represent native methods more closely as AmbientTalk methods.
333Like this:
334- we don't need to convert AT selectors into Java identifiers,
335- we can reuse AT's parameter passing mechanism for native methods (e.g. varargs)
336- we don't have to perform a reflective Java method invocation.
338To change:
339- every NAT class should have a static final HashTable that represents
340the method dictionary of all objects of that NAT type.
342- at startup time, this HashTable should be filled with the native
343methods of that NAT type. Native methods are represented as anonymous
344classes that implement a kind of native_apply method, which is parameterized
346- the Java 'this' (because native methods are static and shared by all
347instances, they are selfless, which should be of the proper NAT type
348[- the AmbientTalk 'self' (necessary if a native method needs late binding))]
349- a call frame that binds formals to actuals
351- meta_invoke on native objects (e.g. NATNil) should query the class-level
352hashtable for the appropriate native method corresponding to the selector,
353and apply the native method. Alternatively, meta_invoke could become a large
354switch statement, but this is harder to maintain + would lead to code duplication
355in between meta_invoke, meta_delegate, meta_select.
357Consider for example the method '+' in NatNumber:
358class NATNumber extends NATNumeric {
359  private final int javaValue_;
360  private static final HashTable _NATNumber_METHODS_ = new HashTable();
361  static {
362    _NATNumber_METHODS_.put(AGSymbol.alloc("+"), new NativeMethod("+",new ATObject[]{AGSymbol("other")}) {
363      public ATObject native_apply(NATNumber ths, ATObject slf, NATCallframe args) {
364        ATObject other = args.meta_select(AGSymbol.alloc("other")); // alternatively, do
365        return ths.base_plus(other.meta_asNumber());
366      }
367    }
368  }
369  public ATNumber base_plus(ATNumber other) { ... }
372Alternatively, to speed up lookup of native methods,
373each native method's selector could be assigned a number, and
374lookup can occur in O(1) by storing the methods in an array where
375the indices correspond to the selector's number.
378Introspective mirrors need to be adapted similarly:
379They should implement native methods with selectors "invoke", "select", etc.
381new NativeMethod("invoke",new ATObject[] { AGSymbol("selector"), AGSymbol("args") }) {
382  public ATObject native_apply(NATMirror m, ATObject self, NATCallframe args) {
383     return up(m.base.meta_invoke("selector").meta_asSelector(),"args").meta_asTable()));
384  }