This shows you the differences between two versions of the page.
Both sides previous revision Previous revision Next revision | Previous revision | ||
at:tutorial:modular [2007/04/24 20:58] tvcutsem added |
at:tutorial:modular [2013/05/17 20:24] tvcutsem adjusted |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Modular Programming ====== | ||
- | < | ||
- | This Tutorial is still under heavy construction! | ||
- | </ | ||
- | |||
- | In this tutorial chapter, we introduce AmbientTalk' | ||
- | |||
- | ===== Namespaces and the lobby ===== | ||
- | |||
- | In AmbientTalk, | ||
- | |||
- | ==== Namespaces ==== | ||
- | In order for AmbientTalk programs to use code defined in other files, a file loading mechanism is required. AmbientTalk provides the concept of a namespace object to perform this task. A namespace object is an object which is internally connected to a directory path on the local file system (how a namespace object is tied to this path will be explained later). By accessing a namespace object' | ||
- | |||
- | < | ||
- | object: { | ||
- | def factorial(n) { | ||
- | if: (n = 0) then: { | ||
- | 1 | ||
- | } else: { | ||
- | n*factorial(n-1) | ||
- | } | ||
- | }; | ||
- | def fib(n) { | ||
- | if: (n <= 1) then: { | ||
- | n | ||
- | } else: { | ||
- | fib(n-1)+fib(n-2) | ||
- | } | ||
- | }; | ||
- | }; | ||
- | </ | ||
- | |||
- | This file can now be loaded by means of the '' | ||
- | < | ||
- | def mathModule := lib.math; | ||
- | system.println(mathModule.factorial(5)); | ||
- | </ | ||
- | |||
- | When the namespace object' | ||
- | * a file named '' | ||
- | * a subdirectory named '' | ||
- | In this example, the file '' | ||
- | |||
- | If the '' | ||
- | |||
- | < | ||
- | By representing hierarchical directories as nested namespace objects, the AmbientTalk programmer can abstract over the actual files and directories of the underlying file system. For example, imagine that the designer of the '' | ||
- | |||
- | 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' | ||
- | </ | ||
- | |||
- | ==== The lobby ==== | ||
- | |||
- | We have yet to explain how namespaces are initially tied to directories. One possibility is to define a '' | ||
- | < | ||
- | root.home.ambienttalkuser.examples.math | ||
- | </ | ||
- | |||
- | The downside of such as scheme is that the absolute path name of the '' | ||
- | |||
- | The [[at: | ||
- | |||
- | < | ||
- | iat -o lib=/ | ||
- | </ | ||
- | |||
- | Whenever a new actor is created by the AmbientTalk interpreter, | ||
- | |||
- | < | ||
- | AmbientTalk provides an alias for the '' | ||
- | </ | ||
- | |||
- | ==== The current namespace ==== | ||
- | |||
- | AmbientTalk provides a slot named '' | ||
- | |||
- | < | ||
- | def discreteMathModule := ~.discretemath; | ||
- | </ | ||
- | |||
- | Loading a module this way is useful because the author of '' | ||
- | |||
- | ===== Importing objects ===== | ||
- | |||
- | 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 definitions become // | ||
- | |||
- | < | ||
- | **Warning: | ||
- | < | ||
- | 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, importing a namespace object often boils down to importing an empty object, obviously not the semantics the programmer had in mind. Loading an entire namespace object is not supported as it would encompass loading all unloaded files in the namespace' | ||
- | </ | ||
- | |||
- | ===== Objects as traits ===== | ||
- | |||
- | In this section, we describe how the '' | ||
- | |||
- | ==== 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 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, '' | ||
- | |||
- | ==== 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. | ||
- | |||
- | ===== Classifying objects using stripes ===== | ||
- | |||
- | 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, | ||
- | |||
- | < | ||
- | defstripe Indexable; | ||
- | </ | ||
- | |||
- | Since a stripe is an abstract type or category, it makes sense to define " | ||
- | |||
- | < | ||
- | defstripe Enumerable; | ||
- | defstripe Ordered; | ||
- | defstripe Sortable <: Enumerable, Ordered; | ||
- | </ | ||
- | |||
- | When defining an object, the object can be striped (tagged) with one or more stripes. | ||
- | |||
- | < | ||
- | def Array := object: { | ||
- | ... | ||
- | } stripedWith: | ||
- | </ | ||
- | |||
- | Finally, the most useful operation defined on stripes is the " | ||
- | |||
- | < | ||
- | is: Array stripedWith: | ||
- | >> true | ||
- | is: Array stripedWith: | ||
- | >> true | ||
- | is: Array stripedWith: | ||
- | >> false | ||
- | </ | ||
- | |||
- | The stripe test determines whether an object //or one of its parents// is striped with the given stripe //or a substripe// of the stripe. | ||
- | |||
- | The stripes with which an object has been tagged can be retrieved by means of the '' | ||
- | |||
- | < | ||
- | stripesOf: Array | ||
- | >> [ < | ||
- | </ | ||
- | |||
- | < | ||
- | Stripes 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 Stripes ==== | ||
- | |||
- | The module ''/ | ||
- | |||
- | < | ||
- | is: 1 stripedWith: | ||
- | >> true | ||
- | is: " | ||
- | >> true | ||
- | </ | ||
- | |||
- | The stripe ''/ | ||
- | |||
- | ==== Stripes as annotated message sends ==== | ||
- | |||
- | In AmbientTalk, | ||
- | |||
- | < | ||
- | obj.m(a, | ||
- | obj.m(a, | ||
- | </ | ||
- | |||
- | In the [[at: | ||
- | |||
- | ===== Exception Handling ===== | ||
- | |||
- | explain: raise, try-catch and variants, first-class handlers, role of stripes, interface of an exception object |