User Tools

Site Tools


at:tutorial:multiparadigm

Differences

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

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
at:tutorial:multiparadigm [2007/06/19 14:44]
tvcutsem *
at:tutorial:multiparadigm [2011/06/07 18:29] (current)
tvcutsem *minor
Line 1: Line 1:
 ====== On Scoping, Closures, Methods and Messages ====== ====== On Scoping, Closures, Methods and Messages ======
  
-This tutorial chapter goes into a bit more detail on the subtle interplay between AmbientTalk's functional aspects (e.g. block closures, higher-order functions and lexical scoping) and its object-oriented aspects (e.g. objects and delegation). It is also described how methods and messages can be manipulated as first-class objects in their own right.+This tutorial chapter goes into a bit more detail on the interplay between AmbientTalk's functional aspects (e.g. block closures, higher-order functions and lexical scoping) and its object-oriented aspects (e.g. objects and delegation). It is also described how methods and messages can be manipulated as first-class objects in their own right.
  
 ===== Lexical Scope vs Object Scope ===== ===== Lexical Scope vs Object Scope =====
Line 13: Line 13:
   - Qualified access to a variable, e.g. ''o.x'', is **always** resolved in the receiver's object scope.   - Qualified access to a variable, e.g. ''o.x'', is **always** resolved in the receiver's object scope.
  
-These rules also hold for method invocation: the invocation ''f()'' is resolved lexically: ''f'' is looked up in the lexical scope; the invocation ''o.m()'' is resolved dynamically, i.e. ''m'' is looked up in ''o''These rules have a large effect on programs: lexical variable access can be statically determined, while qualified access is subject to //late binding// (enabling object-oriented polymorphism). As a programmer, you must be aware of the fundamental difference in semantics.+These rules also hold for method invocation: the invocation ''f()'' is resolved lexically: ''f'' is looked up in the lexical scope; the invocation ''o.m()'' is resolved dynamically, i.e. ''m'' is looked up in ''o''The difference is significant: lexical variable access can be statically determined, while qualified access is subject to //late binding// (enabling object-oriented polymorphism). As a programmer, you must be aware of the fundamental difference in semantics.
  
-Probably the most important conseqence of these rules is that great care has to be taken when an object accesses its own fields or methods. It can now do so in two ways. For example:+The most important consequence of these rules is that one should think carefully about how an object accesses its own fields or methods. It can now do so in two ways. For example:
  
 <code> <code>
Line 48: Line 48:
  
 <note important> <note important>
-For many object-oriented programmers, this distinction between performing ''m()'' and ''self.m()'' may seem confusing and even error-prone. After all, merely "forgetting" the qualified access disallows child objects to override the invocation to ''m''. The confusion stems from the fact that many OO languages -- like Java -- make no distinction between both access forms because they are not truly lexically scopedThey only consider the object scopeand hence unify ''m()'' with ''self.m()'' or ''this.m()''.+For many object-oriented programmers, this distinction between performing ''m()'' and ''self.m()'' may seem confusing and even error-prone. After all, merely "forgetting" the qualified access disallows child objects to override the invocation to ''m''. 
 + 
 +The confusion stems from the fact that many OO languages -- like Java -- make no distinction between both access forms. For examplein Java, if one writes ''m()'' and ''m'' is not lexically visible, it will be interpreted as if the programmer had written ''this.m()''. This solution, however, brings its own set of fragility problems to the table. Gilad Bracha provides a good overview of these in his paper [[http://dyla2007.unibe.ch/?download=dyla07-Gilad.pdf|on the Interaction of Method Lookup and Scope with Inheritance and Nesting]]. In terms of the categorization proposed in that paper, AmbientTalk adopts Bracha's 3rd option in dealing with the interaction between scoping and inheritance.
 </note> </note>
  
Line 67: Line 69:
 It is important that ''assertEquals'' is invoked by means of a self-send and not as ''assertEquals(1+1,1*2)''. The latter would result in an exception because ''assertEquals'' is not lexically visible, it is only visible within ''myTest'''s object scope. It is important that ''assertEquals'' is invoked by means of a self-send and not as ''assertEquals(1+1,1*2)''. The latter would result in an exception because ''assertEquals'' is not lexically visible, it is only visible within ''myTest'''s object scope.
  
-==== Nesting Objects ====+===== Nesting Objects =====
  
 AmbientTalk exploits its lexical scoping rules to the fullest extent by allowing as much program elements as possible to be nested. For example, it is possible to lexically nest objects within other objects, or even to nest functions within other functions. In this section, we describe how nested objects interact with the scoping rules presented above. AmbientTalk exploits its lexical scoping rules to the fullest extent by allowing as much program elements as possible to be nested. For example, it is possible to lexically nest objects within other objects, or even to nest functions within other functions. In this section, we describe how nested objects interact with the scoping rules presented above.
  
-=== Facets ===+==== Facets ====
  
 One of the most appealing use cases for nesting objects is that it allows a very secure kind of //sharing// between objects: all objects that are nested within another object have the privilege of all sharing the same lexical scope. This form of sharing is much more secure that sharing data via delegation, because the set of objects sharing the scope is statically fixed. In the E language, such nested objects are called [[http://www.erights.org/elib/capability/ode/ode-objects.html|facets]]. One of the most appealing use cases for nesting objects is that it allows a very secure kind of //sharing// between objects: all objects that are nested within another object have the privilege of all sharing the same lexical scope. This form of sharing is much more secure that sharing data via delegation, because the set of objects sharing the scope is statically fixed. In the E language, such nested objects are called [[http://www.erights.org/elib/capability/ode/ode-objects.html|facets]].
Line 97: Line 99:
 The ''distrustedFunction'' only has a reference to the ''reader'' object, allowing it to only invoke the ''read'' method. Because ''reader'' is lexically nested within the ''cell'', it has privileged access to the ''cell'''s fields, which it shares //only// with the ''writer'' object. If ''reader'' and ''writer'' would have shared ''cell'' by means of delegation, the ''distrustedFunction'' would still be able to access the ''contents'' field by means of delegation. Because ''reader'' is lexically nested within ''cell'' and the lexical scope is inaccessible to regular objects, the ''cell'' object remains entirely encapsulated w.r.t. ''distrustedFunction''. The ''distrustedFunction'' only has a reference to the ''reader'' object, allowing it to only invoke the ''read'' method. Because ''reader'' is lexically nested within the ''cell'', it has privileged access to the ''cell'''s fields, which it shares //only// with the ''writer'' object. If ''reader'' and ''writer'' would have shared ''cell'' by means of delegation, the ''distrustedFunction'' would still be able to access the ''contents'' field by means of delegation. Because ''reader'' is lexically nested within ''cell'' and the lexical scope is inaccessible to regular objects, the ''cell'' object remains entirely encapsulated w.r.t. ''distrustedFunction''.
  
-=== Nesting and Delegation ===+==== Nesting and Delegation ====
  
 Thanks to AmbientTalk's lexical scoping rules, nested objects have full access to the fields and methods of lexically enclosing objects. Note that within a nested object, the ''self'' pseudo-variable does refer to the nested object itself, as expected. Also, it is perfectly legal for a lexically nested object to have its own dynamic parent, for example: Thanks to AmbientTalk's lexical scoping rules, nested objects have full access to the fields and methods of lexically enclosing objects. Note that within a nested object, the ''self'' pseudo-variable does refer to the nested object itself, as expected. Also, it is perfectly legal for a lexically nested object to have its own dynamic parent, for example:
Line 129: Line 131:
 In the example above, writing ''self.assertEquals(a,b)'' would fail because ''self'' is then bound to ''inner'' which does not delegate to ''UnitTest''. Similarly, writing ''assertEquals(a,b)'' would fail because it is not lexically visible to ''inner'', it is only visible in the object scope of ''theTest''. In the example above, writing ''self.assertEquals(a,b)'' would fail because ''self'' is then bound to ''inner'' which does not delegate to ''UnitTest''. Similarly, writing ''assertEquals(a,b)'' would fail because it is not lexically visible to ''inner'', it is only visible in the object scope of ''theTest''.
  
-==== Methods vs Closures ====+===== Methods vs Closures =====
  
 As mentioned previously, AmbientTalk not only allows objects to be nested within other objects, but also allows functions to be nested within other functions. At this point, it becomes important to distinguish between //closures// and //methods//. As mentioned previously, AmbientTalk not only allows objects to be nested within other objects, but also allows functions to be nested within other functions. At this point, it becomes important to distinguish between //closures// and //methods//.
Line 152: Line 154:
 In the above example, ''meth'' is a method of ''o'' because it is defined as a function directly within an object clause. ''clo'' on the other hand, is a closure because it is nested within another method, so it does not belong to an object directly. This example also shows why methods do not close over ''self'' and closures do. A method can be thought of as a closure which is explicitly parameterized with an extra ''self'' variable, filled in by the interpreter upon method invocation. A closure is not parameterized with this extra variable, it simply inherits the value of ''self'' from its nested method. In the above example, ''meth'' is a method of ''o'' because it is defined as a function directly within an object clause. ''clo'' on the other hand, is a closure because it is nested within another method, so it does not belong to an object directly. This example also shows why methods do not close over ''self'' and closures do. A method can be thought of as a closure which is explicitly parameterized with an extra ''self'' variable, filled in by the interpreter upon method invocation. A closure is not parameterized with this extra variable, it simply inherits the value of ''self'' from its nested method.
  
-=== Top-level Functions ===+==== Top-level Functions ====
  
 Top-level functions are actually methods in AmbientTalk. This is because all top-level code is treated as the initialization code of that file's module object. Hence, it is legal to use ''self'' in top-level functions. Top-level functions are actually methods in AmbientTalk. This is because all top-level code is treated as the initialization code of that file's module object. Hence, it is legal to use ''self'' in top-level functions.
  
-=== Block closures ===+==== Block closures ====
  
 Block closures, as created by means of the syntax ''{ |args| body }'' always evaluate to closures and hence always capture the value of ''self''. Block closures, as created by means of the syntax ''{ |args| body }'' always evaluate to closures and hence always capture the value of ''self''.
  
-==== External Methods ====+===== External Methods and Fields =====
  
-It is possible to define methods externally on an object. For example:+It is possible to define methods and fields externally on an object. For example:
  
 <code> <code>
Line 177: Line 179:
  
 <note important> <note important>
-There is one more difference between closures and external methods: next to the special treatment of ''self'', ''super'' is also treated specially for external methods. Within an externally added method, ''super'' will refer to the parent object of the object to which the method is added, not the parent of the object that defined the external method. This means that in effect, an external method captures the lexical scope save the bindings for ''self'' and ''super''.+There is one more difference between closures and external methods: next to the special treatment of ''self'', ''super'' is also treated specially for external methods. Within an externally added method, ''super'' will refer to the parent object of the object to which the method is added, not the parent of the object that defined the external method. This means that in effect, an external method captures the lexical scope and the bindings for ''self'' and ''super''.
  
 The rationale behind this decision is that ''self'' and ''super'' are inherently related to the object hierarchy of which a method is a part. Hence, even if a method is defined externally on an object, ''super'' should properly refer to the parent object of which the method is a part, rather than the lexically visible value of ''super''. The rationale behind this decision is that ''self'' and ''super'' are inherently related to the object hierarchy of which a method is a part. Hence, even if a method is defined externally on an object, ''super'' should properly refer to the parent object of which the method is a part, rather than the lexically visible value of ''super''.
 +</note>
 +
 +<note warning>
 +External methods, while powerful, introduce many subtle issues in the language. Therefore, this feature may disappear from later releases of AmbientTalk. It is therefore wise to stay clear from using this feature. For an overview of the problem introduced by external field or method declarations, see Gilad Bracha's blog post on [[http://gbracha.blogspot.com/2008/03/monkey-patching.html|monkey patching]].
 </note> </note>
  
Line 186: Line 192:
 In AmbientTalk, methods can be accessed as first-class entities. When methods are represented as first-class entities, they are represented as closures whose lexical scope corresponds to the object in which they have been defined. Their hidden ''self'' parameter is bound to the object from which the method was "selected". In AmbientTalk, methods can be accessed as first-class entities. When methods are represented as first-class entities, they are represented as closures whose lexical scope corresponds to the object in which they have been defined. Their hidden ''self'' parameter is bound to the object from which the method was "selected".
  
-Methods become first-class when they are selected from existing objects, as if they were fieldsFor example, it is possible to write:+Methods become first-class when they are selected from existing objects. Because of the [[at:tutorial:objects#uniform_access|uniform access principle]] introduced previously, it is not possible to "select" a method ''m'' from an object ''o'' simply by writing ''o.m''. Recall that this would instead //execute// the method with zero arguments. So, in order to gain access to a first-class representation of the method, AmbientTalk introduces the //selection operator// ''&'':
  
 <code> <code>
Line 193: Line 199:
 }; };
  
-def squareClo := Math.square;+def squareClo := Math.&square;
 > squareClo(2) > squareClo(2)
 >> 4 >> 4
 </code> </code>
  
-When a field access results in a method rather than a field slot, the method slot is automatically lifted into a closure. The closure can subsequently be passed around as a stand-alone value. When the method closure is invoked, the original method is run with ''self'' bound to the object from which the method was selected. Here's another example:+Slot selection automatically lifts a method slot into a closure. The closure can subsequently be passed around as a stand-alone value. When the method closure is invoked, the original method is ran with ''self'' bound to the object from which the method was selected. Here's another example:
  
 <code> <code>
Line 210: Line 216:
   };   };
 }; };
-> [1,2,3].map: Adder.new(1).addTo+> [1,2,3].map: Adder.new(1).&addTo
 >> [2,3,4] >> [2,3,4]
 </code> </code>
  
 The ''addTo'' method is selected as a closure from a new ''Adder'' object whose ''amount'' field is initialized to 1. Subsequently, that closure is applied to each element of a table, yielding a new table whose values are incremented by one. The ''addTo'' method is selected as a closure from a new ''Adder'' object whose ''amount'' field is initialized to 1. Subsequently, that closure is applied to each element of a table, yielding a new table whose values are incremented by one.
 +
 +The selection operator is also required when accessing lexically visible methods as first-class closures:
 +
 +<code>
 +def o := object: {
 +  def m() { ... };
 +  def grabTheMethod() { &m };
 +}
 +</code>
 +
 +Selection using ''&'' adheres to the uniform access principle in the sense that it is possible to select a field from an object, just like it is possible to select a method from an object. When selecting a field from an object, the resulting closure is an //accessor// for the field, i.e. a nullary closure that upon application returns the field's value:
 +
 +<code>
 +def o := object: {
 +  x := 5;
 +};
 +def f := o.&x;
 +> f
 +>> <native closure:x>
 +> o.x := 6; f()
 +>> 6
 +</code>
 +
 +In the same vein, one may select a mutator method for a field ''x'' by evaluating ''&x:=''. A mutator is a unary closure that assigns its single argument to the field it encapsulates.
  
 ===== First-class Messages ===== ===== First-class Messages =====
at/tutorial/multiparadigm.1182257073.txt.gz · Last modified: 2007/06/25 21:13 (external edit)