User Tools

Site Tools


at:tutorial:actors

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Next revisionBoth sides next revision
at:tutorial:actors [2008/08/01 14:44] – * tvcutsemat:tutorial:actors [2008/11/04 10:16] – added tvcutsem
Line 70: Line 70:
   };   };
 }; };
->><far ref to:<object:11600335>>+>><far ref:behaviour of <actormirror:14115383>>
 </code> </code>
  
Line 138: Line 138:
  
 <note warning> <note warning>
-A word of warning: isolates are objects that are (deep) copied freely between actors. As a result, they should be objects whose actual object identity is of little importance. Usually, the identity of by-copy objects is determined by the value of some of the object's fields. Therefore, it is good practice to override the ''=='' method on isolates to compare isolates based on their semantic identity, rather than on their object identity. For example, equality for complex numbers should be defined as:+Isolates are objects that are (deep) copied freely between actors. As a result, they should be objects whose actual object identity is of little importance. Usually, the identity of by-copy objects is determined by the value of some of the object's fields. Therefore, it is good practice to override the ''=='' method on isolates to compare isolates based on their semantic identity, rather than on their object identity. For example, equality for complex numbers should be defined as:
 <code> <code>
 def ==(other) { def ==(other) {
Line 144: Line 144:
 } }
 </code> </code>
 +
 +On a related note, it is good practice to consider isolates as //immutable// objects, since modifying an isolate will only modify its local copy.
 </note> </note>
  
Line 169: Line 171:
 >>4 >>4
 </code> </code>
 +
 +<note>
 +Since AmbientTalk 2.12 the interpreter is smart enough to figure out the lexically free variables of an isolate itself. If no variables are explicitly listed, the interpreter will try to figure out which lexically free variables it should implicitly copy. Unlike explicitly listed variables (like ''|x|'' in the above example), these implicitly copied free variables are private to the isolate (so they cannot be accessed from outside the isolate).
 +</note>
  
 ===== Futures ===== ===== Futures =====
Line 319: Line 325:
  
 <code> <code>
 +// to create an explicit future:
 def [future, resolver] := makeFuture(); def [future, resolver] := makeFuture();
-consumer<-give(future); + 
-def val := /* calculate useful value */+// to explicitly resolve a future
 resolver.resolve(val); resolver.resolve(val);
 </code> </code>
Line 328: Line 335:
  
 The resolver also defines a ''ruin(e)'' method which can be used to explicitly ruin a future, causing any attached ''when:becomes:catch:'' blocks to trigger their ''catch:'' clause. The resolver also defines a ''ruin(e)'' method which can be used to explicitly ruin a future, causing any attached ''when:becomes:catch:'' blocks to trigger their ''catch:'' clause.
 +
 +As an example of such conditional synchronization, consider the following example first proposed by Henry Lieberman in his 1987 paper on "Object-oriented Programming in ACT-1". A dating service allows lonely hearts to register their profile allowing it to match people based on their profiles. The dating service can be modelled as an object:
 +
 +<code>
 +def makeDatingService() {
 +  def people := []; // a list of Questionnaire objects
 +  object: {
 +    def match(lonelyHeart) {
 +      // if an ideal mate is found in the list,
 +      //   return its name
 +      // otherwise
 +      //   create a future (a "promise") to answer
 +      //   the lonelyHeart later, when an ideal made
 +      //   has registered with the dating service
 +    }
 +  }
 +}
 +</code>
 +
 +Let us assume that a person is simply identified by a name and its sex:
 +<code>
 +def makePerson(nam, sx) {
 +  object: {
 +    def name := nam;
 +    def sex := sx;
 +  }
 +};
 +</code>
 +
 +The dating service has a little database stored as a simple list. This list does not contain person objects but rather Questionnaire objects. The questionnaire contains the logic necessary to match people. We will assume for the sake of the example that 2 people match if they are of the opposite sex. In addition, a questionnaire object can keep track of an "outstanding answer" (which we model as a first-class future) for a lonely heart that is still waiting for his or her perfect match.
 +
 +<code>
 +def makeQuestionnaire(p) {
 +  def idealPersonResolver;
 +  object: {
 +    def person := p;
 +    def matches(otherQ) { otherQ.person.sex != p.sex };
 +    def wait() {
 +      def [future, resolver] := makeFuture();
 +      idealPersonResolver := resolver;
 +      future
 +    };
 +    def notify(name) { idealPersonResolver.resolve(name) };
 +  };
 +};
 +</code>
 +
 +When a questionnaire is asked to ''wait()'', it returns a future and stores the future's corresponding resolver in a hidden field. Later, when a matching person is found, the future can be explicitly resolved by invoking the questionnaire's ''notify'' method.
 +
 +Armed with these abstractions, we can now fill in the missing logic of the ''match'' method of the dating service:
 +
 +<code>
 +def makeDatingService() {
 +  def people := []; // a list of Questionnaire objects
 +  object: {
 +    def match(lonelyHeart) {
 +      def lonelyHeartQ := makeQuestionnaire(lonelyHeart);
 +      { |return|
 +        people.each: { |idealMateQ|
 +          // an ideal mate was found
 +          if: (idealMateQ.matches(lonelyHeartQ).and: { lonelyHeartQ.matches(idealMateQ) }) then: {
 +            idealMateQ.notify(lonelyHeart.name); // notify idealMate
 +            // remove the person from the database
 +            people := people.filter: { |q| q != idealMateQ };
 +            return(idealMateQ.person.name) // notify lonelyHeart
 +          }
 +        };
 +        // no ideal mate was found, store its questionnaire in the database
 +        people := people + [lonelyHeartQ];
 +        lonelyHeartQ.wait(); // return a future for the ideal person's name
 +      }.escape();
 +    }
 +  }
 +}
 +</code>
 +
 +Below, we define an auxiliary method that illustrates how a lonely heart has to interact with the dating service.
 +
 +<code>
 +def d := makeDatingService();
 +def register(p) {
 +  when: d<-match(p)@FutureMessage becomes: { |name|
 +    system.println(p.name + " matched with " + name);
 +  };
 +};
 +</code>
 +
 +The key to our conditional synchronization is that the ''when:becomes:'' listener will only be invoked if and when a matching person has registered itself with the dating service. As long as this condition has not been fulfilled, the future will remain unresolved. In fact, the future returned by ''d<-match(p)'' has been resolved with another future, the one returned by ''Questionnaire.wait()''. However, recall that a future resolved by another unresolved future does not really count as being "resolved" and will only trigger its listeners once its "dependent" future has been resolved.
 +
 +The complete source code of the above example can be found in the file ''at/demo/DatingService.at'' found in the distribution. The directory ''at/demo'' also contains an example solution to Dijkstra's famous "dining philosophers" problem, which makes use of a similar technique to achieve conditional synchronization of the philosophers.
  
 ===== Actor Mirrors ===== ===== Actor Mirrors =====
Line 372: Line 469:
 </code> </code>
  
-If both the ''outer'' and ''inner'' actors lexically see ''x'', they could modify it concurrently, reintroducing race conditions on the internal state of an actor. Therefore, when defining an actor using a block of code, we disallow access to the enclosing lexical scope by the new actor. It is as if it was defined at top-level. Hence, actors behave similarly to [[#isolates|isolates]] in this respect. The above example is incorrect in that ''inner'' will not be able to read or modify ''x''.+If both the ''outer'' and ''inner'' actors lexically see ''x'', they could modify it concurrently, reintroducing race conditions on the internal state of an actor. Therefore, when defining an actor using a block of code, we disallow direct access to the enclosing lexical scope by the new actor. Actors behave similarly to [[#isolates|isolates]] in this respect. The above example code will work, but the programmer has to keep in mind that the ''x'' variable accessed by ''inner'' is a //copy// of the ''x'' variable of ''outer''. Hence, assignments to ''x'' by ''inner'' will not affect ''outer'' and vice versa.
  
-Recall that isolates could be given selective access to their enclosing lexical scope by specifying accessed variables as formal parameters to their initializing closure, which gave rise to copying the variable into the isolateWe allow actors to do the same. Hence, the above example can be written properly as:+Recall that isolates could be given access to their enclosing lexical scope either by specifying accessed variables as formal parameters to their initializing closure or by having the interpreter derive the lexically free variables automaticallyIf the programmer wants to make explicit the fact that ''x'' is copied, the example can also be rewritten as:
  
 <code> <code>
at/tutorial/actors.txt · Last modified: 2020/02/05 21:26 by elisag