/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

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