User Tools

Site Tools


at:tutorial:objects

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
at:tutorial:objects [2007/07/10 22:21] – changed tvcutsemat:tutorial:objects [2025/06/19 16:09] (current) – Removing uniform access information elisag
Line 94: Line 94:
   def z := 0;   def z := 0;
   def sumOfSquares() {   def sumOfSquares() {
-    super^sumOfSquares() + z*z+    super^sumOfSquares() + z*z;
   };   };
 } }
Line 159: Line 159:
 def Enumerable := object: { def Enumerable := object: {
   def collect: closure {   def collect: closure {
-    def c := clone: self;+    def c := self.new([]);
     self.each: { |v|     self.each: { |v|
-      c.add(closure(v))+      c.add(closure(v));
     };     };
 +    c;
   };   };
 }; };
 def Array := object: { def Array := object: {
   def elements := [];   def elements := [];
-  def init() { ... };+  def init(a) { elements := a; }; 
 +  def add(v) { elements := elements + [v]; self };
   def collect: closure {   def collect: closure {
     Enumerable^collect: closure;     Enumerable^collect: closure;
Line 173: Line 175:
   def each: clo {   def each: clo {
     1.to: elements.length do: { |i|     1.to: elements.length do: { |i|
-      clo(elements[i])+      clo(elements[i]);
     };     };
   };   };
Line 181: Line 183:
 A message sent to an object using the ''^'' symbol (e.g. to the ''Enumerable'' object in the example above) will start the method lookup in this object and execute the method body with the ''self'' pseudovariable **left unchanged** to the message sender. In the code example above, when ''collect:'' is invoked on an ''Array'' object, the array object //delegates// the message to the ''Enumerable'' object. As such, method lookup starts in ''Enumerable'', finds the method there, and then invokes it with ''self'' left bound to the ''Array'' object. Hence, the ''self.each:'' send in the ''Enumerable'' object uses ''Array'''s definition of ''each:'' to generate the elements in the collection. A message sent to an object using the ''^'' symbol (e.g. to the ''Enumerable'' object in the example above) will start the method lookup in this object and execute the method body with the ''self'' pseudovariable **left unchanged** to the message sender. In the code example above, when ''collect:'' is invoked on an ''Array'' object, the array object //delegates// the message to the ''Enumerable'' object. As such, method lookup starts in ''Enumerable'', finds the method there, and then invokes it with ''self'' left bound to the ''Array'' object. Hence, the ''self.each:'' send in the ''Enumerable'' object uses ''Array'''s definition of ''each:'' to generate the elements in the collection.
  
-Of course, the example above is a bit contrived: we could have just assigned ''Enumerable'' as the parent of ''Array'' such that we would not even have to write the "delegating" ''collect:'' method in ''Array''. However, what the explicit ''^'' delegation operator allows is the expression of patterns resembling //multiple inheritance// where some requests are delegated to one object, while other methods can be delegated to other objects. Explicit delegation enables the expression of delegation patterns which would be awkward or difficult to express using only single (delegation-based) inheritance. In [[:at:tutorial:modular|a later chapter]], we will show how ''^'' forms the basis for advanced //trait composition//.+<code> 
 +Array.add(1).add(2).add(3) 
 +def c := Array.collect: { |v| v+1 } 
 +c.each: { |v| system.print(v)} // prints 234 
 +</code> 
 + 
 +Of course, the example above is a bit contrived: we could have just assigned ''Enumerable'' as the parent of ''Array'' such that we would not even have to write the "delegating" ''collect:'' method in ''Array''. However, what the explicit ''^'' delegation operator allows is the expression of patterns resembling //multiple inheritance// where some requests are delegated to one object, while other methods can be delegated to other objects. Explicit delegation enables the expression of delegation patterns which would be awkward or difficult to express using only single (delegation-based) inheritance. In [[:at:tutorial:modular#objects_as_traits|a later chapter]], we will show how ''^'' forms the basis for advanced //trait composition//.
  
 Having described the semantics of ''^'', we can now turn our attention to "super-sends". In AmbientTalk, a traditional "super-send" (ala Java or Smalltalk) is expressed as explicit delegation to the ''super'' object. For example, here is how to properly initialize parent objects from child objects: Having described the semantics of ''^'', we can now turn our attention to "super-sends". In AmbientTalk, a traditional "super-send" (ala Java or Smalltalk) is expressed as explicit delegation to the ''super'' object. For example, here is how to properly initialize parent objects from child objects:
Line 202: Line 210:
  
 ===== Encapsulation ===== ===== Encapsulation =====
-In AmbientTalk, all fields and methods are "public" via selectionStill, a field or method can be made "private" by means of lexical scoping. The following code shows the definition of an object inside the definition of a function. The fields and methods of this object cannot be accessed directly from outside the funuction.+ 
 +AmbientTalk has no notion of "visibility modifiers" for fields or methods. All fields and methods of an object are considered "public"Nevertheless, a field or method can be made "private" to a scope by means of lexical scoping (a technique sometimes referred to as [[http://www.erights.org/elib/capability/ode/ode-objects.html|lambda abstraction]]). The following code shows the definition of an object inside the definition of a function. Although all of the object'fields and methods are public, the object can make use of lexically visible fields and methods of outer objects or functions which are not simply accessible "from the outside":
  
 <code> <code>
-def makeObject(hidden) { +def makeBankAccount(balance) { 
-    object: { +  object: { 
-      def foo() { /* use hidden */ } +    def deposit(amnt) { 
-    }+      balance := balance + amnt; 
 +      "ok" 
 +    };
   }   }
 +}
 </code> </code>
  
-Due to the encapsulation of this object the following instruction fails:+Because the bank account object encapsulates its ''balance'' in its private, lexical scope, the following code fails:
  
 <code> <code>
-makeObject(5).hidden+makeBankAccount(100).balance
->>Lookup failure : selector hidden could not be found in  +>>Lookup failure : selector balance could not be found in  
-  <object:5068254>+  <obj:{super,super:=,deposit}>
 </code> </code>
 +
 +This pattern of creating objects by means of "constructor functions" rather than by cloning and instantiating prototypes is often very useful in its own right. However, when creating objects that use their lexical scope to hold their state, be aware of the following issue when you mix object creation via instantiation and object creation via cloning: clones //share// their lexical scope! Hence, given a bank account object ''b'', the following leads to erroneous behaviour:
 +
 +<code>
 +def b := makeBankAccount(100);
 +def b2 := b.new(); // shares its balance field with b!
 +b.deposit(10); // affects b2 as well!
 +</code>
 +
 +In order to prevent this kind of errors, it is considered best practice to override ''new'' for objects that should be solely defined by means of constructor functions, as follows:
 +
 +<code>
 +def makeBankAccount(balance) {
 +  object: {
 +    def new(@args) { makeBankAccount(@args) };
 +    def deposit(amnt) { /* as before */ };
 +  }
 +}
 +</code>
 +
 +By overriding ''new'' and calling the constructor function, this code ensures that code such as ''aBankAccount.new(10)'' will result in a proper new bank account object with its own private lexical scope to store its state.
 +
at/tutorial/objects.1184098910.txt.gz · Last modified: 2007/07/10 22:27 (external edit)