at:tutorial:modular
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
at:tutorial:modular [2007/04/22 17:26] – added tvcutsem | at:tutorial:modular [2013/05/17 20:24] (current) – adjusted tvcutsem | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Modular Programming ====== | ====== Modular Programming ====== | ||
- | |||
- | < | ||
- | This Tutorial is still under heavy construction! | ||
- | </ | ||
In this tutorial chapter, we introduce AmbientTalk' | In this tutorial chapter, we introduce AmbientTalk' | ||
Line 42: | Line 38: | ||
* a file named '' | * a file named '' | ||
* a subdirectory named '' | * a subdirectory named '' | ||
- | In this example, | + | In this example, |
If the '' | If the '' | ||
Line 50: | Line 46: | ||
Obviously, this approach works both ways: if the library designer had started out organizing his project using subdirectories and multiple files, he can always restructure the library' | Obviously, this approach works both ways: if the library designer had started out organizing his project using subdirectories and multiple files, he can always restructure the library' | ||
- | < | + | </note> |
==== The lobby ==== | ==== The lobby ==== | ||
Line 61: | Line 57: | ||
The downside of such as scheme is that the absolute path name of the '' | The downside of such as scheme is that the absolute path name of the '' | ||
- | The [[at: | + | The [[at: |
< | < | ||
Line 68: | Line 64: | ||
Whenever a new actor is created by the AmbientTalk interpreter, | Whenever a new actor is created by the AmbientTalk interpreter, | ||
+ | |||
+ | Think of AmbientTalk' | ||
< | < | ||
Line 85: | Line 83: | ||
===== Importing objects ===== | ===== Importing objects ===== | ||
- | explain: import | + | The previous section has shown how the result of evaluating the content of an external file can be accessed from within another file. One file almost always defines multiple useful function or object definitions. As shown previously, multiple definitions can be returned to clients loading the file by returning an object and making the definitions fields or methods of the returned object. We sometimes refer to such objects as //module objects//, although they are ordinary objects with no special properties. |
+ | |||
+ | As shown in the example code above, functionality from a module object can be used simply by accessing its fields or invoking one of its methods. Again, it is not wrong to think of any object as a small kind of module. Sometimes, functionality from a module object is used so often in an importing file that it is worth redefining the function such that it can be accessed unqualified. For example, if a lot of factorials have to be calculated, it is worth defining: | ||
+ | |||
+ | < | ||
+ | def factorial := mathModule.factorial; | ||
+ | </ | ||
+ | |||
+ | The factorial function has been selectively // | ||
+ | |||
+ | < | ||
+ | import mathModule; | ||
+ | </ | ||
+ | |||
+ | is equivalent to: | ||
+ | |||
+ | < | ||
+ | def factorial := mathModule.factorial; | ||
+ | def fib := mathModule.fib; | ||
+ | </ | ||
+ | |||
+ | When an object has been imported, all of its local slots become lexically visible. This, of course, introduces the danger that previously lexically visible | ||
+ | |||
+ | <note warning> | ||
+ | **Warning:** it may seem useful to sometimes be able to import a namespace object. For example, it may seem useful to import the '' | ||
+ | < | ||
+ | import lib; | ||
+ | system.println(math.factorial(5)); | ||
+ | </ | ||
+ | |||
+ | However, it is advised not to import namespace objects like this. The reason is that a namespace object is initially empty, and adds slots to itself dynamically whenever a slot corresponding to a file or subdirectory is accessed for the first time. Hence, | ||
+ | </ | ||
===== Objects as traits ===== | ===== Objects as traits ===== | ||
- | explain: using import as an object-composition | + | In this section, we describe how the '' |
- | ===== Classifying objects using stripes ===== | ||
- | explain: what are stripes? what kind of objects are they, stripe subtyping, stripe | + | ==== import as trait composition ==== |
+ | |||
+ | As an example, consider the typical functionality of an " | ||
+ | |||
+ | < | ||
+ | def Enumerable := object: { | ||
+ | // map the closure over the collection | ||
+ | def collect: clo { | ||
+ | def result := []; | ||
+ | self.each: { |e| result := result + [clo(e)] }; | ||
+ | result | ||
+ | }; | ||
+ | // return an element in enumeration for which pred returns true | ||
+ | def detect: pred { | ||
+ | { |return| | ||
+ | self.each: { |e| | ||
+ | if: pred(e) then: return(e) | ||
+ | }; | ||
+ | nil }.escape(); | ||
+ | }; | ||
+ | // return all elements for which pred returns false | ||
+ | def reject: pred { | ||
+ | def result := []; | ||
+ | self.each: { |e| if: !pred(e) then: { result := result + [e] } }; | ||
+ | result | ||
+ | }; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | As can be seen from the method bodies of the '' | ||
+ | |||
+ | Using the '' | ||
+ | |||
+ | < | ||
+ | def Range := object: { | ||
+ | import Enumerable; | ||
+ | def start := 0; | ||
+ | def end := 0; | ||
+ | def init(from, | ||
+ | start := from; end := to; | ||
+ | }; | ||
+ | def each: clo { | ||
+ | start.to: end-1 do: clo | ||
+ | }; | ||
+ | }; | ||
+ | Range.new(0, | ||
+ | >> | ||
+ | </ | ||
+ | |||
+ | In this example, '' | ||
+ | |||
+ | < | ||
+ | def Range := object: { | ||
+ | // import Enumerable is translated into: | ||
+ | def collect: clo { Enumerable^collect: | ||
+ | def detect: pred { Enumerable^detect: | ||
+ | def reject: pred { Enumerable^reject: | ||
+ | ... // previous definitions from Range | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | So, '' | ||
+ | |||
+ | Note that in AmbientTalk, | ||
+ | |||
+ | < | ||
+ | def Enumerable := object: { | ||
+ | def collect: clo { /* as before */ }; | ||
+ | def detect: pred { /* as before */ }; | ||
+ | def reject: pred { /* as before */ }; | ||
+ | def each: clo @Required; | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==== Resolving conflicts: exclusion and aliasing ==== | ||
+ | |||
+ | One of the advantages of trait composition is that any conflicts (name clashes) are resolved at trait composition time. Contrast this with e.g. mixin-based composition, | ||
+ | |||
+ | The '' | ||
+ | |||
+ | < | ||
+ | // do not import the slots collect: and detect: | ||
+ | import Enumerable exclude collect:, detect: | ||
+ | // do not import collect: and import reject: as remove: | ||
+ | import Enumerable alias reject: := remove: exclude collect: | ||
+ | </ | ||
+ | |||
+ | If the compositor defines an alias for an imported slot, it is good practice to ensure that the compositor has (or imports) a slot with the original name as well. That way, if the trait object performs a self-send to invoke one of its own methods, it will find a matching slot in the compositor. If the compositor aliases a slot and does not define the slot itself, a lookup by the trait of the original slot name would fail. | ||
+ | |||
+ | < | ||
+ | '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | Moreover, all methods of the trait object annotated with '' | ||
+ | </ | ||
+ | |||
+ | ===== Classifying objects using type tags ===== | ||
+ | |||
+ | In class-based languages, classes are a useful tool for categorising objects. Each object is an instance of some class, and sometimes it is useful to be able to ask to which class an object belongs. However, it is well-known by proponents of object-oriented programming that explicitly referring to the class of an object breaks that object' | ||
+ | |||
+ | Nevertheless, | ||
+ | |||
+ | < | ||
+ | deftype Indexable; | ||
+ | </ | ||
+ | |||
+ | Since a type tag is an abstract type or category, it makes sense to define subtype relations on them. Also, since type tags do not carry an implementation, | ||
+ | |||
+ | < | ||
+ | deftype Enumerable; | ||
+ | deftype Ordered; | ||
+ | deftype Sortable <: Enumerable, Ordered; | ||
+ | </ | ||
+ | |||
+ | When defining an object, the object can be tagged with one or more type tags. | ||
+ | |||
+ | < | ||
+ | def Array := object: { | ||
+ | ... | ||
+ | } taggedAs: [ Indexable, Sortable ] | ||
+ | </ | ||
+ | |||
+ | Finally, the most useful operation defined on type tags is the "type test": it allows objects to test whether an object is tagged with the appropriate type tag. | ||
+ | |||
+ | < | ||
+ | is: Array taggedAs: Indexable | ||
+ | >> true | ||
+ | is: Array taggedAs: Ordered | ||
+ | >> true | ||
+ | is: Array taggedAs: Set | ||
+ | >> false | ||
+ | </ | ||
+ | |||
+ | The type test determines whether an object //or one of its parents// is tagged with the given type tag //or a subtype// of the type tag. | ||
+ | |||
+ | The type tags with which an object has been tagged can be retrieved by means of the '' | ||
+ | |||
+ | < | ||
+ | tagsOf: Array | ||
+ | >> [ <type tag: | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | Type tags can best be compared to empty Java interface types. Such empty interfaces are sometimes used in Java purely for the purposes of marking an object. Examples are '' | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==== Native Type Tags ==== | ||
+ | |||
+ | The module ''/ | ||
+ | |||
+ | < | ||
+ | is: 1 taggedAs: / | ||
+ | >> true | ||
+ | is: " | ||
+ | >> true | ||
+ | </ | ||
+ | |||
+ | The type ''/ | ||
+ | |||
+ | ==== Type tags as annotated message sends ==== | ||
+ | |||
+ | In AmbientTalk, | ||
+ | |||
+ | < | ||
+ | obj.m(a, | ||
+ | obj.m(a, | ||
+ | </ | ||
+ | |||
+ | In the [[at: | ||
===== Exception Handling ===== | ===== Exception Handling ===== | ||
- | explain: raise, | + | AmbientTalk employs a traditional exception handling mechanism based on try-blocks. The '' |
+ | |||
+ | < | ||
+ | try: { | ||
+ | calculateSomething(); | ||
+ | } catch: DivisionByZero using: { |e| | ||
+ | system.println(e.message); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The first argument is a closure to execute, delineating a dynamic piece of code to protect with the given exception handler. The third argument is a one-argument closure, invoked when an exception is caught of the right kind. The second argument is a type tag. By default, exceptions are handled based on their type tags. A hierarchy of type tags is used to classify exceptions. Exception handling in AmbientTalk is quite conventional: | ||
+ | |||
+ | Raising an exception is done by means of the '' | ||
+ | |||
+ | < | ||
+ | if: (denominator == 0) then: { | ||
+ | raise: XDivisionByZero.new(" | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Note that a new instance of an object named '' | ||
+ | |||
+ | < | ||
+ | deftype DivisionByZero <: lobby.at.lang.types.Exception; | ||
+ | def XDivisionByZero := lobby.at.lang.exceptions.createException(DivisionByZero); | ||
+ | </ | ||
+ | |||
+ | ==== Exceptions raised by the interpreter ==== | ||
+ | |||
+ | The '' | ||
+ | |||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | ==== Custom exception handlers ==== | ||
+ | |||
+ | You can specify any object to act as a first-class | ||
+ | |||
+ | < | ||
+ | try: { | ||
+ | calculateSomething(); | ||
+ | } using: (object: { | ||
+ | // this is a first-class handler object | ||
+ | def canHandle(exc) { | ||
+ | is: exc taggedAs: DivisionByZero | ||
+ | }; | ||
+ | def handle(exc) { | ||
+ | system.println(exc.message); | ||
+ | }; | ||
+ | } taggedAs: [/ | ||
+ | </ | ||
+ | |||
+ | First-class exception handlers are sometimes useful to factor out common exception handling behaviour, and also to specify more complex boolean conditions, e.g. one can express that only '' | ||
+ | |||
+ | ==== Catching multiple kinds of exceptions ==== | ||
+ | |||
+ | AmbientTalk defines a number of convenience methods that allow you to list up to three '' | ||
+ | |||
+ | < | ||
+ | try: { | ||
+ | calculateSomething(); | ||
+ | } catch: DivisionByZero using: { |e| | ||
+ | system.println(e.message); | ||
+ | } catch: NoSolution using: { |e| | ||
+ | calculateSomethingElse(); | ||
+ | }; | ||
+ | </ | ||
+ | |||
+ | If more clauses are required, either nested '' | ||
+ | |||
+ | Care has to be taken that handlers are listed in increasing order of " | ||
+ | |||
+ | ===== Escaping Continuations ===== | ||
+ | |||
+ | It is often useful to be able to abort the control flow within a method prematurely. In traditional imperative programming languages, this is done by means of a '' | ||
+ | |||
+ | < | ||
+ | def contains(tbl, | ||
+ | { |return| | ||
+ | 1.to: tbl.length do: { |i| | ||
+ | if: (tbl[i] == elt) then: { | ||
+ | return(true) | ||
+ | } | ||
+ | }; | ||
+ | false | ||
+ | }.escape() | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | When '' | ||
+ | |||
+ | As can be seen, an escaping continuation is more general than a return statement: the continuation is a first-class function object and can hence be passed on to other objects. Hence, it is possible to return from multiple nested function calls at once. | ||
+ | |||
+ | There is an important limitation to the use of escaping continuations. An escaping continuation is not a full-fledged continuation (such as the one provided by Scheme' |
at/tutorial/modular.1177255579.txt.gz · Last modified: 2007/04/22 17:29 (external edit)