Differences

This shows you the differences between the selected revision and the current version of the page.

at:tutorial:appendix 2008/07/10 14:28 at:tutorial:appendix 2020/04/28 19:47 current
Line 1: Line 1:
-====== Appendix ======+====== Appendix: Libraries ======
-In the appendix, we explain useful libraries available to the AmbientTalk/2 programmer. These libraries provide abstractions ranging from traditional, established "collections" up to newly researched language constructs, such as "ambient references".+In the appendix, we explain useful libraries available to the AmbientTalk/2 programmer as part of the AmbientTalk standard library, also known as ''atlib''. These libraries provide abstractions ranging from traditional, established "collections" up to newly researched language constructs, such as "ambient references". 
 + 
 +The Ambientalk standard library (''atlib'') is part of the AmbientTalk/2 distribution. Note that the Intellij plugin already contains ''atlib''. If you would like to access the atlib source files, please visit the dedicated gitlab project [[ https://gitlab.soft.vub.ac.be/ambienttalk/atlib |here.]]
===== Unit Testing Framework ===== ===== Unit Testing Framework =====
Line 27: Line 29:
This will execute all ''test*'' methods in the given unit test (in an **undefined** order!), and print out which of the tests succeeded or failed. The ''runTest'' method can optionally take a "reporter" object as an argument, which can be used to implement a custom strategy for reporting success or failure of a unit test. The default reporter object is a text-based UI. This will execute all ''test*'' methods in the given unit test (in an **undefined** order!), and print out which of the tests succeeded or failed. The ''runTest'' method can optionally take a "reporter" object as an argument, which can be used to implement a custom strategy for reporting success or failure of a unit test. The default reporter object is a text-based UI.
-Like in JUnit and SUnit, it is possible to define two methods named ''setUp()'' and ''tearDown()'' that are invoked in between //each// individual ''test*'' method. Never rely on the lexical order of your unit test methods for the purposes of initialization, etc.! Unit test methods may be exacuted in an arbitrary order.+Like in JUnit and SUnit, it is possible to define two methods named ''setUp()'' and ''tearDown()'' that are invoked in between //each// individual ''test*'' method. Never rely on the lexical order of your unit test methods for the purposes of initialization, etc.! Unit test methods may be executed in an arbitrary order.
==== Assertions ==== ==== Assertions ====
Line 82: Line 84:
It is also possible to use ''makeFuture()'' to create a fresh future explicitly within the unit test method, and to use the returned resolver to resolve the future at the appropriate time. It is also possible to use ''makeFuture()'' to create a fresh future explicitly within the unit test method, and to use the returned resolver to resolve the future at the appropriate time.
 +
 +<note tip>
 +See the [[distribution#take_offline_remote_objects|distributed programming]] chapter for details about how to simulate network disconnections in distributed unit tests.
 +</note>
==== Test Suites ==== ==== Test Suites ====
Line 102: Line 108:
==== Vector ==== ==== Vector ====
-A vector is a dynamically resizable AmbientTalk table (aka array). Vectors may be created as follows:+A vector is a dynamically resizable AmbientTalk table (aka array). Indexed reading from and writing to a vector is fast (O(1)). Adding elements to a vector is mostly fast, but sometimes requires a resize of the vector. Vectors support the traditional stack operations ''push'' and ''pop'' and may be turned into sets by invoking their ''uniq'' method (note that a ''uniq''-ed vector is not permanently a Set: subsequent duplicates added to the vector will not be filtered). 
 + 
 +Vectors may be created as follows:
<code> <code>
Line 112: Line 120:
<code> <code>
-// returns the number of elements in the vector (not the capacity of the vector!)+// returns the number of elements in the vector (not its capacity!)
length() length()
 +
// returns whether the vector contains elements or not // returns whether the vector contains elements or not
isEmpty() isEmpty()
 +
// is the vector at max. capacity? // is the vector at max. capacity?
atMaxCapacity() atMaxCapacity()
 +
// return idx'th element or raise an IndexOutOfBounds exception // return idx'th element or raise an IndexOutOfBounds exception
at(idx) at(idx)
 +
// write idx'th element or raise IndexOutOfBounds exception // write idx'th element or raise IndexOutOfBounds exception
-//atPut(idx, val)+atPut(idx, val) 
// iterate over the vector // iterate over the vector
each: iterator, returns nil each: iterator, returns nil
 +
// map a unary function over the vector, returns a new vector // map a unary function over the vector, returns a new vector
map: fun map: fun
 +
// accumulate a function with a given starting value // accumulate a function with a given starting value
inject: init into: accum; inject: init into: accum;
 +
// returns a new vector whose elements satisfy "cond" // returns a new vector whose elements satisfy "cond"
filter: cond; filter: cond;
 +
// implode a vector of character strings into one text string // implode a vector of character strings into one text string
implode() implode()
 +
// join a vector of character strings together with the given string // join a vector of character strings together with the given string
join(txt) join(txt)
 +
// returns a range [start,stop[ as a table // returns a range [start,stop[ as a table
select(start, stop) select(start, stop)
-// appends an element to the back of the vector + 
-// returns the vector itself+// appends an element to the back of the vector. Returns the vector itself
add(element) add(element)
// alias for add(element) // alias for add(element)
-def <<(element)+<<(element) 
// insert an element at a given position, causing subsequent elements to shift one pos to the right. Returns this vector // insert an element at a given position, causing subsequent elements to shift one pos to the right. Returns this vector
insert(atPos, element) insert(atPos, element)
 +
// delete the element at the given position, shifts all following elements one pos to the left. Returns the value of the element at the deleted position. // delete the element at the given position, shifts all following elements one pos to the left. Returns the value of the element at the deleted position.
delete(atPos) delete(atPos)
 +
// adds elements to the back of the vector // adds elements to the back of the vector
push(element) push(element)
 +
// deletes elements from the back of the vector // deletes elements from the back of the vector
pop() pop()
-// return the index of the first element matching the given filter (a unary predicate), or nil if none is found+ 
 +// return the index of the first element matching the unary predicate or nil if none is found
find: filter find: filter
 +
// remove the given element from the vector, return true if the element was actually found and deleted, false otherwise // remove the given element from the vector, return true if the element was actually found and deleted, false otherwise
remove(elt, cmp := defaultComparator) remove(elt, cmp := defaultComparator)
 +
// remove all objects for which filter(elt) returns true // remove all objects for which filter(elt) returns true
removeAll: filter removeAll: filter
 +
// destructively appends otherVector to self. Returns this vector // destructively appends otherVector to self. Returns this vector
addAll(otherVector) addAll(otherVector)
 +
// empties the vector // empties the vector
clear() clear()
 +
// Return a new vector whose elements form the set-union of all elements in self U otherVector // Return a new vector whose elements form the set-union of all elements in self U otherVector
union(otherVector, cmp := defaultComparator) union(otherVector, cmp := defaultComparator)
 +
// Return a new vector whose elements form the set-intersection of all elements in self ^ otherVector // Return a new vector whose elements form the set-intersection of all elements in self ^ otherVector
intersection(otherVector, cmp := defaultComparator) intersection(otherVector, cmp := defaultComparator)
 +
// Return a new vector whose elements form the set-difference of self \ otherVector // Return a new vector whose elements form the set-difference of self \ otherVector
difference(otherVector, cmp := defaultComparator) difference(otherVector, cmp := defaultComparator)
 +
// Quicksort the vector in-place. The comparator defines the ordering among elements. // Quicksort the vector in-place. The comparator defines the ordering among elements.
sort(cmp := { |e1,e2| e1 < e2 }) sort(cmp := { |e1,e2| e1 < e2 })
 +
// Turn the vector into a set without duplicates in O(nlogn + n) // Turn the vector into a set without duplicates in O(nlogn + n)
// The vector's ordering is lost (it becomes sorted) // The vector's ordering is lost (it becomes sorted)
uniq(cmp := defaultComparator, ordercmp := {|e1,e2| e1 < e2 }) uniq(cmp := defaultComparator, ordercmp := {|e1,e2| e1 < e2 })
 +
// return an element drawn randomly using a uniform distribution from the array or raise an EmptyVector exception. // return an element drawn randomly using a uniform distribution from the array or raise an EmptyVector exception.
random() random()
 +
// return a table containing all elements of the vector // return a table containing all elements of the vector
asTable() asTable()
</code> </code>
 +
 +The file ''at/collections/vector.at'' contains a unit tests that further helps to illustrate the usage of this Vector abstraction.
==== List ==== ==== List ====
 +
 +The module ''/.at.collections.list'' implements Scheme-like list datastructures. The module exports the prototype ''NIL'', which is bound to the empty list. Non-empty lists are defined as a chain of cons-cells.
 +
 +The list module defines two styles to manipulate cons-cells: an object-oriented and a functional style. The object-oriented style represents cons-cells as ''Cons'' prototypes. Given a cons-cell ''c'', a new one can be constructed by invoking ''c.new(car, cdr)''. The car and cdr part of the cons-cell can be extracted by means of ''c.car'' and ''c.cdr''.
 +
 +The functional style allows one to manipulate lists by means of the following functions:
 +
 +<code>
 +cons(car,cdr) -> a new cons-cell
 +car(conscell) -> the car
 +cdr(conscell) -> the cdr
 +list(@items) -> a cons-cell representing the head of a list
 +</code>
 +
 +Lists (cons-cells or the empty list) support the following operations:
 +
 +<code>
 +// accessors for car and cdr
 +car()
 +cdr()
 +
 +// the length of the list
 +length()
 +
 +// whether the list is empty or not
 +isEmpty()
 +
 +// returns the nth element of the list
 +nth(n)
 +
 +// apply a unary function to each element of the list
 +each: fun
 +
 +// apply a function to each element and its index in the list
 +// i.e. list.eachWithIndex: { |elt, idx| ... }
 +eachWithIndex: fun
 +
 +// map a unary function over the list, returning a new list
 + map: fun
 +
 +// accumulate a value over a list
 +inject: init into: accum
 +
 +// return a new list whose elements satisfy the unary predicate
 +filter: cond
 +
 +// does the list contain the element?
 +contains(elt, cmp := DEFAULTCOMPARATOR)
 +
 +// implode or join a list of text strings
 +implode()
 +join(txt)
 +
 +// drop the first n elements from the list
 +tail(n)
 +
 +// prepend an element to the list
 +add(elt)
 +
 +// insert an element in the list (functionally)
 +insert(atPos, element)
 +
 +// return a new list where the element atPos is deleted
 +delete(atPos)
 +
 +// functional append
 +append(aList)
 +
 +// return the index of the first matching element, or nil if none is found
 +find: filter
 +
 +// return the index in the list of the element or nil of not found
 +indexOf(elt, cmp := DEFAULTCOMPARATOR)
 +
 +// return a list where the given element is removed
 +remove(elt, cmp := DEFAULTCOMPARATOR)
 +
 +// return a new list where all objects for which filter(elt) is true are removed
 +removeAll: filter
 +
 +// convert the list into a table
 +asTable()
 +</code>
 +
 +The file ''at/collections/list.at'' contains a unit test that further illustrates the usage of the list datastructure.
 +
 +
 +===== Top-level functions =====
 +
 +The file ''at/init/init.at'' shipped with the AmbientTalk/2 system library contains the code that is evaluated on startup within //every// actor created in the system. Because the definitions are evaluated in every actor's top-level scope, these  definitions will be globally visible in every file. Below, we describe the standard functionality provided by AmbientTalk/2's default ''init'' file.
 +
 +==== Asynchronous control structures ====
 +
 +The ''init'' file defines a number of useful control structures that operate asynchronously.
 +
 +''loop:'' defines an infinite asynchronous loop. That is, the block closure is executed, then asynchronously applied again:
 +<code>
 +loop: {
 +  ...
 +}
 +</code>
 +
 +An ''if''-test on a future for a boolean:
 +<code>
 +whenTrue: booleanFuture then: { ... } else: { ... }
 +</code>
 +
 +Asynchronous while loop over future-type conditional:
 +<code>
 +asLongAs: { /* asynchronous computation returning a future */ } do: { ... }
 +</code>
 +
 +==== Mobile code ====
 +
 +The function ''script:carrying:'' can be used to define a "pass-by-copy" closure, as follows:
 +
 +<code>
 +def mobileAdder(x) {
 +  script: { |n| x + n } carrying: [`x]
 +}
 +</code>
 +
 +A call to ''mobileAdder(5)'' returns a closure which, when applied to a number, returns that number incremented with 5. Unlike regular closures, which are pass-by-far-reference when passing them to another actor, the above closure is pass-by-copy. The result is that a remote actor can apply the closure synchronously. The catch is that for this to work, the closure must specifically list all of its lexically free variables in the ''carrying:'' parameter. These variables will be copied along with the closure when it is parameter-passed.
 +
 +The constructor function ''isolate:passAs:'' allows you to define an isolate object with a custom serialization strategy. For example,
 +
 +<code>
 +def foo := 42;
 +def i := isolate: {
 +  ...
 +} passAs: { |foo|
 +  /.some.Object.new(foo);
 +}
 +</code>
 +
 +The above code defines an isolate object ''i'' which, when passed between actors, becomes a ''some.Object'' on the other side. Note that state (''foo'' in the example) can be transferred as usual via the parameter list of the closure.
 +
 +===== Custom Exceptions =====
 +
 +The module ''/.at.exceptions'' defines a number of auxiliary methods which can be used to define one's own custom exceptions. Here is how to define a custom exception ''FooException''. First, define a new type tag with which clients of your code can catch the exception:
 +
 +<code>
 +deftype FooException;
 +</code>
 +
 +Next, define a prototype exception object using the ''createException'' function exported by the exception module. As a convention, an exception prototype object is prefixed with ''X'':
 +
 +<code>
 +def XFooException := createException(FooException);
 +</code>
 +
 +''XFooException'' is now bound to an object which is tagged with the given type tag, and which implements two methods: ''stackTrace'', which returns an AmbientTalk stack trace for the exception, and ''message'', which returns a string indicating what went wrong. The object also has a constructor taking a new message as an argument. You can now raise your custom exception as follows:
 +
 +<code>
 +raise: XFooException.new("reason for what went wrong");
 +</code>
 +
 +If your custom exception requires additional state, you can define it as an extension of the prototype exception. If you define a custom constructor, do not forget to initialise the parent object, as follows:
 +
 +<code>
 +deftype IndexOutOfBounds;
 +def XIndexOutOfBounds := createException(IndexOutOfBounds) with: {
 +  def min;
 +  def max;
 +  def idx;
 +  def init(min, max, idx) {
 +    super^init("Index out of bounds: given " + idx + " allowed: [" + min + "," + max + "]");
 +    self.min := min;
 +    self.max := max;
 +    self.idx := idx;
 +  };
 +}
 +</code>
 +
 +The exception module also exports an auxiliary function ''error(msg)'' which can be used to raise a "quick and dirty" runtime exception with a given message. It also exports the prototypes of a number of standard exceptions that can be raised by the language runtime itself.
 +
 +===== Language Extensions =====
 +
 +The files in the ''at/lang'' directory define custom language features which mostly use AmbientTalk/2's reflective facilities to extend the language.
 +
 +
 +
 +
 +==== Futures ====
 +
 +The module ''/.at.lang.futures'' provides support for futures. Futures have already been described as part of the [[:at:tutorial:actors#futures|concurrency]] section in the tutorial.
 +
 +The module exports the type tags ''OnewayMessage'', ''FutureMessage'' and ''Due'':
 +  * Tagging an asynchronous message with ''FutureMessage'' will attach a future to the message.
 +  * Tagging a message with ''OnewayMessage'' ensures no future will ever be attached to the message.
 +  * Tagging a message with ''@Due(timeout)'' associates a future with the message that is automatically ruined with a ''TimeoutException'' after the given ''timeout'' period (in milliseconds) has elapsed.
 +
 +Messages can be automatically associated with a future by invoking the ''enableFutures()'' function, which enables futures for all messages, except those tagged as a ''OnewayMessage''.
 +
 +The futures module also exports the function ''when:becomes:'' to await the resolution of a future, and auxiliary ''when:becomes:catch:using:'' functions.
 +
 +Futures can also be created and resolved manually:
 +<code>
 +import /.at.lang.futures;
 +def [fut, res] := makeFuture();
 +when: someAsynchronousComputation() becomes: { |value|
 +  res.resolve(value); // resolve the future manually
 +}
 +fut // return the future to a client
 +</code>
 +
 +The ''makeFuture'' function can also take a timeout. If a timeout is given it returns a returns a pair [lease, resolver]  where the lease timer gets immediately activated. If the future is not resolved within the given timeout, the lease expires and ruins the future with a ''TimeoutException''. Note that this means a lease will get parameter-passed rather than the future if given to other actors.
 +
 +=== Auxilary functions in the futures module ====
 +
 +The futures module also provides some auxiliary functions, of which ''group:'' is often a very useful one. The ''group:'' construct groups a table of futures into a single future which is resolved with a table of values or ruined with an exception:
 +
 +<code>
 +when: (group: [ a<-m()@FutureMessage, b<-n()@FutureMessage ]) becomes: { |values|
 +  def [aResult, bResult] := values;
 +  ...
 +}
 +</code>
 +
 +Another useful auxilary function is ''future:'' construct which returns a future which is resolved with the value passed to the 'reply' closure:
 +
 +<code>
 +future: { |return|
 +  // some computation
 +  return(val)
 +}
 +</code>
 +
 +This is actually equivalent to the slightly more verbose code:
 +
 +<code>
 +def [fut,res] := makeFuture();
 +try: { // some computation
 +  res.resolve(val);
 +} catch: Exception using: { |e| res.ruin(e) }
 +fut;
 +</code>
 +
 +==== Multifutures ====
 +
 +The module ''/.at.lang.multifutures'' provides support for multifutures. A multifuture is a future that can be resolved multiple times. We distinguish between 'bounded multifutures', which can be resolved up to a maximum number and 'unbounded multifutures' which have no upper bound.
 +
 +A multifuture is constructed as follows:
 +<code>
 +def [mf, resolver] := makeMultiFuture(n, timeout);
 +</code>
 +
 +The parameter ''n'' indicates the maximum number of values/exceptions with which the future can be resolved/ruined. If ''n'' is ''nil'', the multifuture is unbounded. The timeout parameter is optional. If not nil, it is a timeout period in milliseconds that causes the multifuture to //automatically// become fully resolved after the provided timeout. Once fully resolved, a multifuture will not accept any new values/exceptions, even if it has not reached its "upper bound" ''n'' yet.
 +
 +A multifuture accepts the following listeners:
 +
 +<code>
 +whenEach: multiFuture becomes: { |val| ... }
 +</code>
 +
 +The above listener is invoked whenever the future is resolved with a new value. Its code can thus be executed multiple times.
 +
 +<code>
 +whenAll: multiFuture resolved: { |values|
 +  ...
 +} ruined: { |exceptions| ... }
 +</code>
 +
 +The above listener is invoked if all results have been gathered (only possible if the maximum number of results is known) or when the ''timeout'' period associated with the future has elapsed. ''values'' refers to a table of all resolved values. If there are no exceptions, only the first code block is triggered. If there are only exceptions, the first block is still invoked with an empty table.
 +
 +Note the following properties of multifutures:
 +  * It is allowed to register a whenAll:resolved:ruined: listener an 'unbounded' multifuture. However, for such multifutures, this listener will only trigger if a timeout was specified during the multifuture's creation. The listener is invoked upon timeout, and later incoming results are discarded.
 +  * As with futures, it is legal to send asynchronous messages to the multifuture, which are in turn propagated to all resolved values. If some values are ruined, asynchronous messages containing a multifuture are ruined. Hence, exceptions only propagate through a pipeline of multifutures.
 +  * When a multifuture A is resolved with a multifuture B, all of B's eventual values/exceptions become values/exceptions of A.
 +  * A whenEach:becomes: observer automatically returns a multifuture itself. This multifuture has the same arity as the original and is resolved/ruined with the return values of the multiple invocations of the becomes: or catch: closures.
 +  * Like with futures, multifutures can be explicitly created, e.g.:
 +<code>def [ multifut, resolver ] := makeMultiFuture(upperBound);</code>
 +  * Multifutures can be attached to messages by annotating an asynchronous message with the @Gather(n) type tag.
 +  * Adding a when:becomes: listener on a multifuture is allowed but only triggers for the first value/exception of the multifuture. This allows multifutures to be used wherever regular futures are expected.
 +
 +The multifutures module also exports an abstraction known as a "multireference". The expression ''multiref: [ ref1, ref2,... ]'', where ''refi'' are far references, returns a multireference. Any message sent to a multireference is sent to all constituent references, and a multifuture is returned which can trap the results.
 +
 +When the message sent to a multireference is annotated with @Due(t), the timeout is applied to the implicit multifuture, causing whenAll observers to trigger automatically. Note that the implicit multifuture of a multireference is bounded, so whenAll observers trigger automatically when all replies have been received.
 +
 +
 +==== Leased Object References ====
 +
 +The module ''/.at.lang.leasedrefs'' provides support for leased object references. Leased object references have already been described as part of the [[:at:tutorial:distribution#dealing_with_permanent_failures|distributed programing]] section in the tutorial.
 +
 +<note>
 +The implementation of leased object references actually consists of two files: ''/.at.lang.leasedrefs'' and ''/at.lang.leasedrefstrait''. ''leasedrefstrait'' module implements the behaviour common to the different types of leased references. This module is imported in the ''leasedrefs'' module which provides the public API for creating and managing leased object references.
 +</note>
 +
 +The ''leasedrefs'' module exports language constructs to create three different types of leased object references:
 +  * ''lease:for:''returns a leased reference that remains valid for the indicated time interval.
 +  * ''renewOnCallLease:for:'' returns a leased reference that is automatically renewed (with the specified time interval) each time the remote object receives a message.
 +  * ''singleCallLease:for:'' returns a leased reference that remains valid for only a single method call on the remote object.
 +
 +Variations of these constructs are also provided to allow developers to specify the renewal time interval in renew-on-call leased references and the name(s) of the method(s) which trigger expiration of a single-call leased reference.
 +
 +The ''leasedrefs'' module also provides the following constructs to explicitly manage the lifetime of leased references:
 +
 +<code>
 +renew: leasedRef for: interval; // renews a lease
 +revoke: leasedRef; // revokes a lease
 +leaseTimeLeft: leasedRef; // return time left until lease expires
 +when: lease expired: {...}; // trigger a closure when the lease expires
 +</code>
 +
 +The ''when:expired:'' construct is provided to schedule clean-up actions with a leased reference upon its expiration.
 +
 +Finally, the ''leasedrefs'' module exports support primitives to manipulate time intervals (i.e. ''minutes'', ''seconds'', ''millisecs'') so that developers do not need to explicitly import the timer module to use leased references.
 +
 +
 +
 +==== TOTAM ====
 +
 +The module ''/.at.lang.totam'' provides an implementation for TOTAM, a tuple space model geared towards mobile ad hoc networks which combines a replication-based tuple space model with a dynamic scoping mechanism that limits the transportation of tuples.
 +
 +Please have a look to [[:uf:totam]] for further details on the model and its API.
 +
 +==== Dynamic Variables ====
 +
 +The module ''/.at.lang.dynvars'' provides support for defining and using 'Dynamic Variables'. Dynamic variables 'simulate' dynamically scoped variables and are often used to parameterize large parts of code. For example, the 'current output stream'. A dynamic variable has the advantage over a simple global variable that it can only be assigned a value for the extent of a block of code.
 +
 +A dynamic variable can be defined as follows:
 +<code>
 +def name := dynamicVariable: initialValue;
 +</code>
 +
 +It can be read as follows:
 +<code>
 +?name or name.value
 +</code>
 +
 +It can be assigned only within a limited dynamic scope, as follows:
 +<code>
 +with: name is: newval do: { code }
 +// or
 +name.is: newval in: { code }
 +</code>
 +
 +When ''code'' terminates (either normally or via an exception), the dynamic variable is automatically reset to its previous value.
 +
 +By convention, we prefix the names of dynamic variables with a ''d'', e.g. ''dTimeoutPeriod''. This makes it easier to remember to access these variables by means of ''?'' or ''.value''.
 +
 +You can find more usage examples of dynamic variables in the unit test included in the file ''at/lang/dynvars.at''.
 +
 +==== Ambient References ====
 +
 +Ambient references are defined in the module ''/.at.lang.ambientrefs'' . An ambient reference is a special kind of far reference which refers to an ever-changing collection of objects of a certain type. For example:
 +
 +<code>
 +import /.at.lang.ambientrefs;
 +deftype Printer;
 +def printers := ambient: Printer;
 +</code>
 +
 +In the above code, ''printers'' refers to all nearby objects exported by means of  the ''Printer'' type tag. An more in-depth explanation of ambient references can be found on the [[:research:ambientrefs|research page of ambient references]].
 +
 +Ambient references ship with two so-called "implementation modules": the module ''/.at.ambient.ar_extensional_impl'' and the module ''/.at.m2mi.ar_intensional_impl''. By default, the extensional implementation is used, but this can be changed by passing the desired implementation module as a parameter to the ''/.at.lang.ambientrefs'' module.
 +
 +==== Structural Types ====
 +
 +The module ''/.at.lang.structuraltypes'' implements a small library to use structural typing. The library allows for the creation of 'protocols', which are first-class structural types. A structural type is simply a set of selectors. An object o conforms to a protocol P <=> for all selectors s of P, o respondsTo s where respondsTo is determined by o's mirror.
 +
 +A structural type can be branded with type tags. In this case, objects only conform to the type if they are structurally conformant **and** if they are tagged with the structural type's brands.
 +
 +Use the ''protocol:'' function to create a new protocol:
 +
 +<code>
 +def PersonProtocol := protocol: {
 +  def name;
 +  def age;
 +} named: `Person;
 +</code>
 +
 +The ''`Person'' argument is used to give the protocol a name simply for display purposes. The ''object:implements:'' function automatically checks whether an object conforms to any declared types:
 +
 +<code>
 +def tom := object: {
 +  def name := "Tom";
 +  def age() { 24 };
 +} implements: PersonProtocol;
 +</code>
 +
 +You can also create a protocol from an object:
 +<code>def tomsProtocol := protocolOf: tom;</code>
 +
 +You can test protocol conformance in either of two styles:
 +  * ''does: tom implement: PersonProtocol'' => true or false
 +  * ''PersonProtocol <? tom'' => true or false
 +
 +You can also force a ''StructuralTypeMismatch'' exception to be raised if the object does not conform to the type:
 +  *  ''ensure: tom implements: PersonProtocol'' => true or exception
 +  *  ''PersonProtocol.checkConformance(tom)'' => true or exception
 +
 +More usage examples of structural types can be found in the unit test defined in the file ''at/lang/structuraltypes.at''.
 +
 +==== Traits ====
 +
 +The module ''/.at.lang.traits'' exports a small library to use AmbientTalk's traits in a more structured manner. In the literature, traits are described as reusable components with two interfaces: an interface of methods that are //provided// by the trait //to// the composite and an interface of methods that are //required// by the trait //from// the composite. AmbientTalk's traits only make the provided interface explicit. The required interface remains implicit and unchecked at composition time.
 +
 +Using the ''traits'' module, a trait can specify that it requires its composite to adhere to a certain protocol (i.e. a structural type, cf. the previous section). Using traits in this way requires an explicit composition step where the trait's requirements are checked.
 +
 +To define a "structured" trait, define your  trait objects as follows:
 +<code>
 +trait: {
 +  ...
 +} requiring: Protocol;
 +</code>
 +
 +The above code creates a trait that can only be composed into an object adhering to the specified protocol. To compose traits, use the following language construct:
 +
 +<code>
 +object: {
 +  use: {
 +    import T1 exclude ...;
 +    import T2 alias ...;
 +  }
 +}
 +</code>
 +
 +The ''use:'' block can **only** include ''import'' statements. It simply executes the ''import'' statements, but in addition checks whether the composite, //after// having imported all of its traits, provides all of the methods specified in the required protocol of its imported traits.
 +
 +Note that the place where ''use:'' is used inside an object matters: if one of the traits requires a method ''m()'' that is defined only later in the composite, the check will fail. To avoid this, place the ''use:'' block at the bottom of the object declaration.
 +
 +Usage examples can be found in the unit tests in the file ''at/lang/traits.at''.
 +
 +===== Utilities =====
 +
 +The files in the ''at/support'' subdirectory of the standard library implement various utilities of use to the AmbientTalk programmer. We discuss the most useful modules below.
 +
 +
 +==== Timing Utilities ====
 +
 +The module ''/.at.support.timer'' provides utility functions to schedule code for execution at a later point in time. Its most useful control construct is the following:
 +
 +<code>
 +def subscription := when: timeoutPeriod elapsed: {
 +  ...
 +}
 +</code>
 +
 +The ''when:elapsed:'' function takes as its arguments a timeout period (in milliseconds) and a block closure and schedules the closure for execution after the given timeout period. The function returns a subscription object whose single ''cancel'' method can be used to abort the execution of the scheduled code. Once ''cancel'' has been invoked, it is guaranteed that the closure will no longer be executed by the timer module.
 +
 +The milliseconds used to define the timeout period must be provided as a Java ''long'' value. To construct such a value from an AmbientTalk number, the timer module defines the following auxiliary functions:
 +
 +  * ''millisec(ms)'' => convert AmbientTalk number to a Java long value representing a timeout period in milliseconds.
 +  * ''seconds(s)'' => convert AmbientTalk number to a Java long value representing a timeout period in seconds.
 +  * ''minutes(m)'' => convert AmbientTalk number to a Java long value representing a timeout period in minutes.
 +
 +Additionally, the timer module defines a function ''now()'' which returns the current system time as a Java long value.
 +
 +The timer module also defines a function ''whenever:elapsed:'' which repetitively invokes the given block closure every time the timeout period has elapsed. The returned subscription object can be used to eventually stop the repetitive invocation of the closure.
 +
 +Finally, there is a variant of ''when:elapsed:'' called ''when:elapsedWithFuture:'' which returns a future that will be resolved or ruined by executing the given closure after the given timeout. This variant is very useful in unit tests, e.g:
 +
 +<code>
 +def testAsyncNearbyPlayerReply(){
 +  def nearbyPlayers := // search 2 nearby player orjbects;
 +  // wait a bit so that there are the 2 members.
 +  when: 2.seconds elapsedWithFuture:{
 + self.assertEquals(2, nearbyPlayers.getReceivedAnswers);
 +  }
 +};
 +</code>
 +
 +
 +==== Logging Framework ====
 +
 +The module ''/.at.support.logger'' defines a tiny logging framework akin to the well-known [[http://logging.apache.org/log4j|Log4J]] logging framework for Java.
 +
 +Here's a typical example of how to use a logger:
 +<code>
 +import /.at.support.logger;
 +def log := makeLogger("my prefix", INFO); // do not log DEBUG
 +log("a message", ERROR); // message level is optional and defaults to INFO
 +</code>
 +
 +The ''makeLogger'' function returns a function which can be used to log messages to an output object. It takes three arguments, all of which are optional: a string to be prefixed to every logged message (default ''""''), a logging level (default ''DEBUG'') and an output object (default ''system'').
 +
 +The logging level determines which messages are shown on the output log. The available error levels are: ''NONE'', ''DEBUG'', ''WARNING'', ''INFO'', ''ERROR'' and ''FATAL'' in order of exceeding importance. Hence, a log whose default is ''WARNING'' will not show ''DEBUG''-level messages.
 +
 +The output object is an object that understands ''println(string)''. It is used by the logging framework to write its log to the screen, a file, etc.
 +
 +==== Object Inspector ====
 +
 +The module ''/.demo.inspector'' implements a graphical object inspector. The module requires ''java.awt'' and ''javax.swing'' support from the underlying JVM running AmbientTalk. To inspect an object ''o'', execute:
 +
 +<code>
 +import /.demo.inspector;
 +inspect(o);
 +</code>
 +
 +This will pop up a graphical inspector on the object, listing the object's fields and methods. The object's fields and methods can recursively be inspected through the graphical user interface of the object inspector.
 +
 +==== Symbiosis Utilities ====
 +
 +The module ''/.at.support.symbiosis'' defines a number of utility functions with respect to the symbiosis with the JVM. It defines the following functions which can be used to quickly create a wrapped Java value of the given primitive type:
 +
 +<code>
 +long(anAmbientTalkNumber) -> aJavaLong
 +short(anAmbientTalkNumber) -> aJavaShort
 +float(anAmbientTalkFraction) -> aJavaFloat
 +byte(anAmbientTalkNumber) -> aJavaByte
 +</code>
 +
 +The module also defines the following function:
 +<code>
 +cast: obj into: Interface
 +</code>
 +
 +The ''Interface'' argument should be a Java class wrapper for an interface type. The function returns a Java proxy object implementing the given interface, wrapping the given AmbientTalk object. If this proxy is subsequently passed to Java code, it will hold that ''proxy instanceof Interface''.
 +
 +==== Miscellaneous ====
 +
 +The module ''/.at.support.util'' is a utility module grouping several miscellaneous tasks.
 +
 +=== Random Numbers ===
 +
 +The utility module defines functions for easily generating random numbers. Its implementation uses the random number generators from the underlying JVM. The following functions are the most useful:
 +
 +<code>
 +// generate a random integer in the interval [min, max[
 +def randomNumberBetween(min, max)
 +// generate a random fraction in the interval [min, max[
 +def randomFractionBetween(min, max)
 +</code>
 +
 +=== Custom Object Serialization ===
 +
 +The method ''uponArrivalBecome:'' exported by the utility module creates a transporter object which can be used in ''pass'' meta-level methods to execute code upon deserialization. The closure passed to this function should return the object with which the transported object should be replaced. For example:
 +
 +<code>
 +//inside a mirror
 +def instancevar := ...;
 +def pass() {
 +  uponArrivalBecome: { |instancevar|
 +    // return object to become here
 +  }
 +}
 +</code>
 +
 +The function plays a role similar to ''readResolve'' in the Java object serialization framework.
 
 
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki