/interpreter/tags/at_build150307/TODO.txt
http://ambienttalk.googlecode.com/ · Plain Text · 379 lines · 300 code · 79 blank · 0 comment · 0 complexity · 2e5258bc81cca0d22c868e2724daca49 MD5 · raw file
- === TODO ===
- ------------------
- Conceptual issues:
- ------------------
- Meta-circular AmbientTalk:
- ==========================
- Goal: write as much as possible AT/2 code in AT/2 itself. A lot of NAT* classes, especially AG* classes
- can be written in AT/2 itself. Most NAT* classes are plain AT/2 objects with a custom mirror that overrides
- only a few operations.
- The only problem is that this makes the implementation much slower as a lot of NAT classes will then become
- NATMirages. We should make our implementation of NATMirage as efficient as possible. We need some kind of
- 'partial behavioural reflection' in order to pay only for the reflection that we need.
- This means that as much meta_* methods should remain hard-wired even for certain mirages, because they are not overridden
- by the mirror.
- For those NAT classes that need access to native Java objects, we might be able to use the Symbiosis.
- As such we may be able to confine the AT/2 kernel to only a handful of classes (NATNil, NATObject, JavaObject)
- Conversions:
- ============
- base_isXXX and base_asXXX methods must become meta_isXXX and meta_asXXX
- I.e. they should be moved to the mirror, in order to minimize the number of base-level methods of normal objects.
- At the base-level meta_isXXX and meta_asXXX should be exposed as natives in the
- root: def isNumber(x) { (reflect: x).isNumber() }
- NATObject implementation should not implement all meta_asXXX methods by creating a coercer by default.
- Should only create a coercer if the object explicitly declares itself to be a number, mirror etc. via a stripe test or something?
- Prototypes:
- ===========
- We need to augment the standard AT/2 library with a namespace full of prototypes.
- E.g. especially exception prototypes such that native exceptions can be raised from within the language.
- at.exceptions.interpreter
- The Standard Library:
- =====================
- Written entirely in AmbientTalk itself. Accessible via 'lobby.at'
- We need:
- - at.init: an initialization file with useful default semantics for most things currently in the OBJLexicalRoot?
- Should also contain isNumber(x), isTable(x) , ... natives.
- - at.collections: a small collections library (binary tree, hashtable, vector, list, queue, set) implementing
- a uniform interface (iteration, collection, filtering, insertion, deletion, lookup)
- - at.unit: a unit testing framework along the lines of JUnit, the unit testing framework of Ruby etc:
- make a sub-object of the TestCase object and implement some methods whose name starts with test*. Running the program should run the tests.
- - at.mirrors: prototypical mirrors on all kinds of language values?
- - at.exceptions: prototype exception objects for native exception types that can be newed and raised.
- Identity and equality:
- ======================
- Next to the == operator, which defines equality through identity, we should introduce
- an operator = which defines structural equality (cfr. eq? versus equal? in Scheme).
- e.g. [2,3] is not == to another [2,3] table, but [2,3] = [2,3]
- == is already implemented and falls back on Java's equals method, which in turn falls back on Java's == operator.
- Structural equality implies deep equality.
- For 'leaf' objects like symbols, numbers, etc., deep equality is identity.
- For 'compound' objects like tables, all elements of the tables must be deep equal.
- The same goes for objects. Objects can be considered as maps from symbols to values, so
- two objects are equal if they map the same symbols to equal values. The problem here
- is that performing deep equality like this is both
- A) very costly
- B) dangerous because it is a graph-traversal
- (have to keep track of already visited objects? even for tables, consider: t[1] := t)
- Another kind of comparison between objects that could be useful is 'substitutability'
- For example, obj1 '<:' obj2 if they have the same field names and method selectors
- (i.e. the 'range' of their conceptual 'map' is similar) such that obj2 can safely
- 'replace' obj1.
- One can then define:
- boolean NATObject.base__opltx__opcol_(ATObject other) {
- foreach field | method selector of this {
- if (other.meta_respondsTo(selector) {
- continue;
- } else {
- return false;
- }
- }
- return dynamicParent_.base__opeql_(other.meta_getDynamicParent());
- }
- Note that structural equivalence of objects is defined in terms of their public fields/methods
- + those of their dynamic parents. Lexical parents are not taken into consideration, as they are
- considered 'private' to the object.
- Symbiosis:
- ==========
- - First-class constructors: jClass.new or jObject.new should wrap a JavaConstructor class,
- such that casting works for constructors as well.
- => Implementation can be sped up a bit using caching. Currently, only methods are cached, could also
- cache Constructors if we introduce a JavaConstructor class? This could also be used to solve problem of
- casting for constructors. E.g. if aClass.new returns a JavaConstructor which is a special kind of JavaClosure,
- then we can do aClass.new.cast(Type1, Type2)(a, b)
- - Solve overloading by being able to annotate a method invocation => future work?
- - methods whose selector contains '_' in Java: we should allow _ in AT identifiers.
- Only disadvantage is that Java identifiers with '_' will be displayed using ':' in AT.
- However, an AT selector will not undergo such conversions when remaining within AT.
- Can be made even cleaner if the _ -> : conversion is only performed for downSelector,
- and for symbiosis introduce another method named 'javaToATSelector'
- Keywords and Optional/Varargs:
- ==============================
- Remember that we wanted some kind of keyworded message sends that
- were matched against a kind of 'regular expression' for stuff like
- try-catch and when-catch, such that e.g. the following invocations
- all trigger the same language construct:
- when: f becomes: { |v| ... }
- when: f becomes: { |v| ... } due: 1000
- when: f becomes: { |v| ... } catch: { #Exc1 |e| ... } catch: { #Exc2 |e| ... }
- This could be done using optional and vararg parameters with keywords:
- def when: future becomes: block due: time := default catch: @blocks {
- ...
- }
- This method actually has a selector when:becomes:[due:](catch:)*
- and regexp matching could be used to match invocation with definition.
- We really need this kind of semantics if we want to make optional
- arguments and varargs work for keywords.
- Meta-send (send-to-mirror):
- ===========================
- Consider introducing another message passing operator (.?) which sends a message
- to an object's mirror rather than to the object itself.
- obj.?msg(args) == (reflect: obj).msg(args).base
- The advantage is that the meta-variant does not require the creation of temporary mirrors.
- The reflect: variant is much more expensive; it requires creating a mirror on obj
- and a mirror on the result, which are usually immediately discarded.
- Next to efficiency considerations, the meta-send is also a more concise way
- of sending messages to mirrors.
- Annotations:
- ============
- Annotations or stripes or types are necessary for multiple reasons:
- - For object classification (e.g. allow objects to categorize themselves
- into domain-specific categories, or tell to the interpreter that they represent
- a table, a function, a number, etc. -> for better coercion)
- - For symbiosis with Java (e.g. objects should tell AmbientTalk which Java
- interfaces they implement)
- - For exception handling (catch only exception objects of a certain kind)
- - For service discovery: need a topic hierarchy to allow interpreter
- to rapidly filter/route discovery events)
- All of these require 'subtyping' semantics
- (i.e. an annotation/stripe/type can have 0 or more supertypes and a subtype is substitutable for a supertype)
- Apart from the above requirements to tag *objects*, sometimes it would be
- interesting to also tag *fields*, *methods*, *message sends* or even *closures*
- Here, these annotations represent declarative directives to the meta-level, which
- e.g. alter field access, method invocation, message dispatch
- - fields: proxy objects that implement get and set
- - methods: proxy objects that implement apply and applyInScope
- - message: proxy implementing sendTo
- - closure: ?
- Especially interesting for introducing proxies (cfr. the 'function transformations' from dPico)
- (custom fields, method wrappers)
- A requirement for these annotations is that they should carry arguments, like:
- obj.msg(a,b,c)#annotation(arg)
- e.g. obj<-msg(a,b,c)#delay(1000)
- Such annotations could e.g. be used to solve overloading problems with the Java symbiosis:
- javaobject.method(a#type1, b#type2, c#type3)
- or one could pass an object tagged with a correct type
- It would be nice to integrate annotations consistently into the language and be able to
- use them both as directives to the meta-level *and* as types for objects.
- Transform annotations into message sends?
- def #foo x := 5 => def x := foo().decorateField(`x, 5) // of decorateField(<field object>)
- => value? should be a custom field object!
- def #bar(1) meth(args) { ... } => def meth := bar(1).decorateMethod(<method object>) => method
- obj.msg(args)#foo => obj <+ foo().decorateMessage(<msg object>) => message
- { #foo |args| body } => foo().decorateClosure({ |args| body }) => closure
- object: #foo { ... } => decorateParameter
- object: { #foo ... } => decorateClosure
- object: { ... }#foo => decorateParameter
- Asynchronous function invocation:
- =================================
- Introduce the shorthand syntax fun<-(a,b,c) to denote fun<-apply([a,b,c])
- such that remote references to closures are more easily invokable.
- Isolates and Import:
- ====================
- Import internally makes use of ClosureMethod objects to implement the delegate methods
- that are imported into the host. These objects are currently not isolates, so when an isolate
- uses import, it will probably lose its delegate methods when being passed by copy.
- Perhaps the methods should be actually copied when being imported into an isolate?
- Import should also become dedicated syntax.
- Far References:
- ===============
- Proper implementation for ==, new and init on far refs
- (== should work, new and init can throw exception)
- Mirrors:
- ========
- Intercessive mirrors are always tied to a particular 'base' object.
- The default intercessive mirror is named 'mirrorroot' and is an object
- that understands all meta_* operations, implementing them using default semantics.
- It can be thought of as being defined as follows:
- def mirrorroot := object: {
- def base := object: { nil } mirroredBy: self // base of the mirror root is an empty mirage
- def init(b) {
- base := b
- }
- def invoke(@args) { <default native invocation behaviour> }
- def select(@args) { <default native selection behaviour> }
- ...
- } stripedWith: [ Mirror ]
- This object can then simply be extended / composed by other objects to deviate from the default semantics.
- Hence, 'mirrors' are simply objects with the same interface as this mirrorroot object: they should be
- able to respond to all meta_* messages and have a 'base' field.
- When a mirror is cloned, its base field is *shared* with the clone. However, the base object will still be
- tied to the original mirror. Therefore, the default clone: operator is implemented as follows:
- def clone: obj {
- if: (is: obj stripedWith: Mirror) then: {
- reflect: (clone: obj.base)
- } else: {
- (reflect: obj).clone()
- }
- }
- In other words: when a mirror is cloned, the mirage is cloned instead (recursively) and a mirror on the new mirage is returned.
- Cloning mirages also clones the corresponding mirror - and re-initializes the cloned mirror's mirage to the cloned mirage, so there
- the bidirectional link between mirror and mirage is not broken.
- There is another way to assign mirrors to mirages, by means of the object:mirroredBy: primitive:
- def mirage := object: {
- ...
- } mirroredBy: aMirror
- Internally, this operation is translated into
- aMirror.new(<uninitialized mirage>)
- which is:
- aMirror.meta_clone().invoke("init", [<uninitialized mirage>])
- For regular mirrors, which are IS-A children of the mirrorroot, this leads to the invocation of 'init' on aMirror,
- and eventually also to the invocation of 'init' on OBJMirrorRoot. This init implementation sets the base field to point
- to the new mirage, and initializes the 'mirror' field of the mirage to the dynamic receiver of 'init'.
- From that point on, the mirage is considered initialized and can be used.
- An 'uninitialized' mirage is one whose mirror is assigned to 'nil'.
- When explicitly making a new instance of a mirror, OBJMirrorRoot checks whether the object
- is receives in its init method is indeed a NATMirage, and whether its mirror is != nil.
- ----------------------
- Implementation issues:
- ----------------------
- Meta-circular AmbientTalk:
- ==========================
- Related to a proper meta-circular implementation of AmbientTalk is to allow as much custom AT objects in
- place of implementation-level objects. That is, we should remove as much .asNative...() casts from the code
- as possible. Each such cast precludes use of custom AT objects. Sometimes, the casting is inevitable (e.g. 1+2),
- sometimes, it is not. E.g.: rather than casting an ATObject to a NATTable and looping over its elements_, one could write:
- if (obj instanceof NATTable) {
- // perform immediate native behaviour
- // loop over ((NATTable) obj).elements_ with for loop
- } else {
- obj.base_asTable().base_each(new NativeClosure(this) {
- // perform native behaviour inside of a native closure's apply method
- });
- }
- Native method sanity check:
- ===========================
- - zorgen dat alle AT interfaces alleen nog base_ / meta_ methoden hebben die InterpreterException throwen
- en die alleen AT args of convertible args returnen / nemen
- violators:
- NATText meta_print() throws InterpreterException;
- alle isXXX() methods => returnen boolean
- ATObject base_object_mirroredBy_(ATClosure code, NATIntercessiveMirror mirror) throws InterpreterException;
- en varianten: nemen expliciet een NATIntercessiveMirror ipv een ATMirror
- Static Variables:
- =================
- We must ensure the integrity of all static variables in the AT/2 implementation with respect
- to race conditions! Static variables are shared by multiple actors. This means that they
- should definitely be final + that the datastructure they point at is immutable. If not,
- e.g. the string pool for symbols, access should be synchronized!
- Native Method Refactoring:
- ==========================
- Goal is to get rid of most methods from the JavaInterfaceAdaptor and Reflection classes,
- and to make native AmbientTalk methods accessible without having to perform a reflective
- Java method invocation.
- Primary goal: to remove all reflective invocations of base_ methods from the implementation.
- Instead, we want to represent native methods more closely as AmbientTalk methods.
- Like this:
- - we don't need to convert AT selectors into Java identifiers,
- - we can reuse AT's parameter passing mechanism for native methods (e.g. varargs)
- - we don't have to perform a reflective Java method invocation.
- To change:
- - every NAT class should have a static final HashTable that represents
- the method dictionary of all objects of that NAT type.
- - at startup time, this HashTable should be filled with the native
- methods of that NAT type. Native methods are represented as anonymous
- classes that implement a kind of native_apply method, which is parameterized
- by:
- - the Java 'this' (because native methods are static and shared by all
- instances, they are selfless, which should be of the proper NAT type
- [- the AmbientTalk 'self' (necessary if a native method needs late binding))]
- - a call frame that binds formals to actuals
- - meta_invoke on native objects (e.g. NATNil) should query the class-level
- hashtable for the appropriate native method corresponding to the selector,
- and apply the native method. Alternatively, meta_invoke could become a large
- switch statement, but this is harder to maintain + would lead to code duplication
- in between meta_invoke, meta_delegate, meta_select.
- Consider for example the method '+' in NatNumber:
- class NATNumber extends NATNumeric {
- private final int javaValue_;
- private static final HashTable _NATNumber_METHODS_ = new HashTable();
- static {
- _NATNumber_METHODS_.put(AGSymbol.alloc("+"), new NativeMethod("+",new ATObject[]{AGSymbol("other")}) {
- public ATObject native_apply(NATNumber ths, ATObject slf, NATCallframe args) {
- ATObject other = args.meta_select(AGSymbol.alloc("other")); // alternatively, do args.at(0)
- return ths.base_plus(other.meta_asNumber());
- }
- }
- }
- public ATNumber base_plus(ATNumber other) { ... }
- }
- Alternatively, to speed up lookup of native methods,
- each native method's selector could be assigned a number, and
- lookup can occur in O(1) by storing the methods in an array where
- the indices correspond to the selector's number.
- Introspective mirrors need to be adapted similarly:
- They should implement native methods with selectors "invoke", "select", etc.
- e.g.
- new NativeMethod("invoke",new ATObject[] { AGSymbol("selector"), AGSymbol("args") }) {
- public ATObject native_apply(NATMirror m, ATObject self, NATCallframe args) {
- return up(m.base.meta_invoke(args.at("selector").meta_asSelector(), args.at("args").meta_asTable()));
- }
- }