Difference between revisions of "Objectless Calls"

m
m (Syntax update)
 
(7 intermediate revisions by 2 users not shown)
Line 1: Line 1:
{{UnderConstruction}}
+
[[Category:ECMA]]
 +
Author: [[User:Paulb| Paul Bates]]
 +
 
 
In this proposal the author highlights some of the advantages of adding a new language mechanism to Eiffel, permitting objectless (known as static in C/C++ and derivative languages) calls of Eiffel implementation. The introduction of objectless calls in Eiffel resolves issues regarding contract guarantees, creation contracts, overuse of "shared" inheritance and can solve a performance related issue where the Eiffel compiler cannot optimize calls because dynamic dispatch.
 
In this proposal the author highlights some of the advantages of adding a new language mechanism to Eiffel, permitting objectless (known as static in C/C++ and derivative languages) calls of Eiffel implementation. The introduction of objectless calls in Eiffel resolves issues regarding contract guarantees, creation contracts, overuse of "shared" inheritance and can solve a performance related issue where the Eiffel compiler cannot optimize calls because dynamic dispatch.
  
Line 14: Line 16:
 
feature {NONE} -- Initialization
 
feature {NONE} -- Initialization
  
     make (a_arg: ?VALUE)
+
     make (a_arg: detachable VALUE)
 
         require
 
         require
 
             a_arg_is_valid_value: is_valid_value (a_arg)
 
             a_arg_is_valid_value: is_valid_value (a_arg)
Line 23: Line 25:
 
feature -- Query
 
feature -- Query
  
     is_valid_value (a_value: ?VALUE): BOOLEAN
+
     is_valid_value (a_value: detachable VALUE): BOOLEAN
 
             -- Determines if an argument is valid
 
             -- Determines if an argument is valid
 
         do
 
         do
Line 39: Line 41:
 
feature -- Query
 
feature -- Query
  
     is_valid_value (a_value: ?VALUE): BOOLEAN
+
     is_valid_value (a_value: detachable VALUE): BOOLEAN
 
             -- Determines if an argument is valid
 
             -- Determines if an argument is valid
 
         do
 
         do
Line 59: Line 61:
 
feature {NONE} -- Initialization
 
feature {NONE} -- Initialization
  
     make (a_arg: ?VALUE)
+
     make (a_arg: detachable VALUE)
 
         require
 
         require
 
             a_arg_is_valid_value: is_valid_value (a_arg)
 
             a_arg_is_valid_value: is_valid_value (a_arg)
Line 70: Line 72:
 
A client now has to create an instance of <e>A_VALIDATOR</e> in order to create an instance of <e>A</e>. The result is a performance hit because of an additional object creation and heap allocation.
 
A client now has to create an instance of <e>A_VALIDATOR</e> in order to create an instance of <e>A</e>. The result is a performance hit because of an additional object creation and heap allocation.
  
<e>process (a_value: ?VALUE)
+
<e>process (a_value: detachable VALUE)
 
     local
 
     local
 
         l_a: A
 
         l_a: A
Line 80: Line 82:
 
     end</e>
 
     end</e>
  
Apart from the fragmentation and additional creation, it seems like the problem is solved. However, take the following adjustments, likely to transpire when an developer other than the original (maybe even the original developer) subclasses <e>A</e> to create a new type <e>B</e>:
+
Apart from the fragmentation and additional creation, it seems like the problem is solved. However, take the following adjustments, likely to transpire when a developer other than the original (maybe even the original developer) subclasses <e>A</e> to create a new type <e>B</e>:
  
 
<e>class B
 
<e>class B
Line 92: Line 94:
 
feature {NONE} -- Initialization
 
feature {NONE} -- Initialization
  
     make (a_arg: ?VALUE)
+
     make (a_arg: detachable VALUE)
 
         do
 
         do
 
             ...
 
             ...
Line 99: Line 101:
 
feature -- Query
 
feature -- Query
  
     is_valid_value (a_value: ?VALUE): BOOLEAN
+
     is_valid_value (a_value: detachable VALUE): BOOLEAN
 
             -- Determines if an argument is valid
 
             -- Determines if an argument is valid
 
         do
 
         do
Line 107: Line 109:
 
end</e>
 
end</e>
  
The affect isn't completely obvious. This is a common scenario and is actually a bug in <e>B</e>. The implementation does not follow the existing model of <e>A</e>, that is to say fragment and implement an additional class <e>B_VALIDATOR</e>. The real implementation needs to be:
+
The effect isn't completely obvious. This is a common scenario and is actually a bug in <e>B</e>. The implementation does not follow the existing model of <e>A</e>, that is to say fragment and implement an additional class <e>B_VALIDATOR</e>. The real implementation needs to be:
  
 
<e>class B_VALIDATION
 
<e>class B_VALIDATION
Line 116: Line 118:
 
feature -- Query
 
feature -- Query
  
     is_valid_value (a_value: ?VALUE): BOOLEAN
+
     is_valid_value (a_value: detachable VALUE): BOOLEAN
 
             -- Determines if an argument is valid
 
             -- Determines if an argument is valid
 
         do
 
         do
Line 139: Line 141:
 
feature {NONE} -- Initialization
 
feature {NONE} -- Initialization
  
     make (a_arg: ?VALUE)
+
     make (a_arg: detachable VALUE)
 
         require
 
         require
 
             a_arg_is_valid_value: is_valid_value (a_arg)
 
             a_arg_is_valid_value: is_valid_value (a_arg)
Line 151: Line 153:
  
 
== Overuse of Inheritance ==
 
== Overuse of Inheritance ==
Due to the lack of an objectless routine mechanism in the Eiffel language there is tenancy to overuse inheritance because it is required. There is a need to use "shared" objects which expose a single instance of a class through a once function. If there was an objectless mechanism classes could just use the a objectless once function. The Eiffel compiler and EiffelStudio IDE make heavy use of shared classes, prefixed <e>SHARED_</e>. In a recent code review <e>FEATURE_I</e> inherits 24 parent classes, only 6 are actual affecting the model and functionality of the class. That is 18 classes inherited for shared access alone!  
+
Due to the lack of an objectless routine mechanism in the Eiffel language there is tenancy to overuse inheritance because it is required. There is a need to use "shared" objects which expose a single instance of a class through a once function. If there was an objectless mechanism classes could just use the a objectless once function. The Eiffel compiler and EiffelStudio IDE make heavy use of shared classes, prefixed <e>SHARED_</e>. In a recent code review <e>FEATURE_I</e> inherits 24 parent classes, of which only 6 are actual affecting the model and functionality of the class. That is 18 classes inherited for shared access alone!  
  
 
The use of inheritance causes another problem, performance degradation.
 
The use of inheritance causes another problem, performance degradation.
Line 158: Line 160:
 
After indicating the problems with the object creation and overuse of inheritance, the issues of performance can be indicated with context.
 
After indicating the problems with the object creation and overuse of inheritance, the issues of performance can be indicated with context.
  
The problem related to creation performance has already been indicated, doubling the object creation, or more conservatively the expensive of an attribute access, just to query argument validity. The problem is exacerbated by making a dynamic call to the validation routine even though none is needed.
+
The problem related to creation performance has already been indicated, doubling the object creation, or more conservatively the expense of an attribute access, just to query argument validity. The problem is exacerbated by making a dynamic call to the validation routine even though none is needed.
  
On that last remark, there are performance implication using shared objects to access a singleton object. All the calls are called on an instance so required the overhead to access the instance, in addition the calls cannot be optimized by the compiler and so are dynamically dispatched - far more expensive on performance than a single static call, which the Eiffel compiler could generate with use of an objectless language addition.
+
On that last remark, there are performance implications in using shared objects to access a singleton object. All the calls are on an instance, and so require the overhead to access the instance; in addition the calls cannot be optimized by the compiler and so are dynamically dispatched - far more expensive on performance than a single static call, which the Eiffel compiler could generate with the use of an objectless language addition.
  
 
== A Proposal ==
 
== A Proposal ==
 
In order to complete this document the author indicates a proposal for such a mechanism for permitting objectless calls.
 
In order to complete this document the author indicates a proposal for such a mechanism for permitting objectless calls.
  
Due to objectless calls being a language mechanism rather than a run-time attribute (such as a once's process/thread status) a new keyword is going to be required, minimizing possible conflicts with feature names use in existing systems. For this proposal the author suggest ''objectless'', indicating a objectless call.
+
Due to objectless calls being a language mechanism rather than a run-time attribute (such as a once's process/thread status) a new keyword is going to be required, minimizing possible conflicts with feature names used in existing systems. For this proposal the author suggests ''objectless'', indicating a objectless call.
  
Objectless class should be permitted on function, both regular and once functions. It is up to debate to indicate if procedures and class attributes are permitted to be objectless
+
Objectless class should be permitted on functions, both regular and once functions. It is up to debate to indicate if procedures and class attributes are permitted to be objectless.
  
 
The '''objectless''' keyword should appear as a routine modifier, as with the <e>frozen</e> keyword. Just as the use of <e>frozen</e> is used to dictate to subclasses a routine cannot be redefined/undefined, '''objectless''' has a similar standing when being called, indicating (not dictating) to clients to use the static calling convention. Secondary to the argument, such placement is clear to a reader the call is objectless. The only alternative foreseeable placement for an '''objectless''' keyword would be a prior to a routine's type keyword (<e>do, once, deferred, external</e>), however in the case of the previous arguments of readability, it's the author's opinion the keyword should not appear there. There may be a suggestion to using a routine's <e>note</e> clause to indicate objectless status but this is a bad idea, not only for readability but because note clauses are informative structures that should not change routine convention.
 
The '''objectless''' keyword should appear as a routine modifier, as with the <e>frozen</e> keyword. Just as the use of <e>frozen</e> is used to dictate to subclasses a routine cannot be redefined/undefined, '''objectless''' has a similar standing when being called, indicating (not dictating) to clients to use the static calling convention. Secondary to the argument, such placement is clear to a reader the call is objectless. The only alternative foreseeable placement for an '''objectless''' keyword would be a prior to a routine's type keyword (<e>do, once, deferred, external</e>), however in the case of the previous arguments of readability, it's the author's opinion the keyword should not appear there. There may be a suggestion to using a routine's <e>note</e> clause to indicate objectless status but this is a bad idea, not only for readability but because note clauses are informative structures that should not change routine convention.
Line 202: Line 204:
 
feature {NONE} -- Initialization
 
feature {NONE} -- Initialization
  
     make (a_arg: ?VALUE)
+
     make (a_arg: detachable VALUE)
 
         require
 
         require
 
             a_arg_is_valid_value: is_valid_value (a_arg)
 
             a_arg_is_valid_value: is_valid_value (a_arg)
Line 211: Line 213:
 
feature -- Query
 
feature -- Query
  
     objectless is_valid_value (a_value: ?VALUE): BOOLEAN
+
     objectless is_valid_value (a_value: detachable VALUE): BOOLEAN
 
             -- Determines if an argument is valid
 
             -- Determines if an argument is valid
 
         do
 
         do
Line 224: Line 226:
 
For clients wanting to create an instance of <e>A</e>, the following code can now be written and guaranteed. There is no need for pulling up the feature <e>is_valid_value</e> into a separate validation class, or the creation of the validation class to perform prospective argument validation.
 
For clients wanting to create an instance of <e>A</e>, the following code can now be written and guaranteed. There is no need for pulling up the feature <e>is_valid_value</e> into a separate validation class, or the creation of the validation class to perform prospective argument validation.
  
<e>process (a_value: ?VALUE)
+
<e>process (a_value: detachable VALUE)
 
         -- Processes a value node.
 
         -- Processes a value node.
 
     local
 
     local
Line 237: Line 239:
 
      
 
      
 
=== Extensions ===
 
=== Extensions ===
One extension to the proposal and possible advantage over languages with a similar mechanism is the possibility to redefine a objectless routine. The semantics of this have clear rules that govern the behavior of calls and the calling convention used. This extension requires consideration now because of impacts on user code in the future if the language is to adopt the extension.
+
One extension to the proposal and possible advantage over languages with a similar mechanism is the possibility to redefine a objectless routine. The semantics of this have clear rules that govern the behavior of calls and the calling convention used. This extension requires consideration now because of possible impacts on user code in the future, if the language is to adopt the extension.
 +
 
 +
Unlike other languages, it should be possible to redefine any objectless routine.
  
 
For the purpose of explaining the semantics of redefinition, the following classes are used.
 
For the purpose of explaining the semantics of redefinition, the following classes are used.
Line 295: Line 299:
 
end</e>
 
end</e>
  
In instance redefinition, when call <e>{A}.f</e> the version from <e>A</e> will be taken because a client called <e>f</e> using the static calling convention. However, attempting to call <e>{B}.f</e> will result in a compile-time error because <e>f</e> is not accessible statically, since it has been redefined as an instance routine.
+
In instance redefinition, when calling <e>{A}.f</e>, the version from <e>A</e> will be taken because a client called <e>f</e> using the static calling convention. However, attempting to call <e>{B}.f</e> will result in a compile-time error because <e>f</e> is not accessible statically, since it has been redefined as an instance routine.
  
 
==== Instance Call Semantics ====
 
==== Instance Call Semantics ====
When calling <e>f</e> on an attached entity, of type <e>A</e> or a subtype, the call semantics behave in the same way as every other call - using dynamic dispatch. If an instance of <e>B</e> is polymorphically assigned to an instance of <e>A</e> and <e>B</e> redefines <e>f</e>, either objectlessly or otherwise, the call is made on the dynamic type of the attached entity, in this case <e>B</e>.
+
When calling <e>f</e> on an attached entity, of type <e>A</e> or a subtype, the call semantics behave in the same way as every other call - using dynamic dispatch. If an instance of <e>B</e> is polymorphically assigned to an instance of <e>A</e> and <e>B</e> redefines <e>f</e>, objectlessly or otherwise, the call is made on the dynamic type of the attached entity, in this case <e>B</e>'s version.
 
+
From within the confines of <e>A</e>, or a subclass of it, an an unqualified call to <e>f</e> the call should be treated in the same way as using an attached entity <e>Current.f</e>, that is, the call is dynamically dispatched. This behavoir can be overridden using the static access calling convention.
+
  
For example, when <e>B</e> redefines <e>f</e>, in <e>A</e>, it can be written <e>{A}.f</e> to ensure the version of <e>f</e> in <e>A</e> is called. Conversely in <e>A</e> calling <e>f</e> unqualified will dispatch the call, resulting in <e>f</e>'s implementation in <e>B</e> being called.
+
From within the confines of <e>A</e>, or a subclass of it, an an unqualified call to <e>f</e> the call should be treated in the same way as using an attached entity <e>Current.f</e>, that is, the call is dynamically dispatched. This behavior can be overridden using the static access calling convention. For example, when <e>B</e> redefines <e>f</e>, in <e>A</e>, it can be written <e>{A}.f</e> to ensure the version of <e>f</e> in <e>A</e> is called. Conversely in <e>A</e> calling <e>f</e> unqualified will dispatch the call, resulting in <e>f</e>'s implementation in <e>B</e> being called.

Latest revision as of 12:01, 8 May 2013

Author: Paul Bates

In this proposal the author highlights some of the advantages of adding a new language mechanism to Eiffel, permitting objectless (known as static in C/C++ and derivative languages) calls of Eiffel implementation. The introduction of objectless calls in Eiffel resolves issues regarding contract guarantees, creation contracts, overuse of "shared" inheritance and can solve a performance related issue where the Eiffel compiler cannot optimize calls because dynamic dispatch.

It is in the author's opinion that objectless calls are a necessary addition for the continued improvement of the Eiffel language, and were overlooked when defining the ECMA standard.

The Creation Problem

To date the Eiffel language suffers from a problem whereby creation contracts cannot be specified correctly or can be specified but not guarenteed. Take the following code with a creation routine needing to validate a creation argument using a class query:

class A
 
create
    make
 
feature {NONE} -- Initialization
 
    make (a_arg: detachable VALUE)
        require
            a_arg_is_valid_value: is_valid_value (a_arg)
        do
            ...
        end
 
feature -- Query
 
    is_valid_value (a_value: detachable VALUE): BOOLEAN
            -- Determines if an argument is valid
        do
            Result := a_value /= Void and then a_value.has_value
        end
 
end

Here there is the classic chicken and egg problem; A client needs to validate any prospective creation argument to create an instance of A but requires an instance of A in order to validate it.

There is a solution but it is far from elegant, fragments class design and opens avenues to bugs. The solution is to refactor and pull up is_valid_value into a validation helper class, assisting A:

class A_VALIDATION
 
feature -- Query
 
    is_valid_value (a_value: detachable VALUE): BOOLEAN
            -- Determines if an argument is valid
        do
            Result := a_value /= Void and then a_value.has_value
        end
 
end

Now there has been a validation class defined, with implementation that actually belongs in A, A can now be defined as:

class A
 
inherit {NONE}
    A_VALIDATION
 
create
    make
 
feature {NONE} -- Initialization
 
    make (a_arg: detachable VALUE)
        require
            a_arg_is_valid_value: is_valid_value (a_arg)
        do
            ...
        end
 
end

A client now has to create an instance of A_VALIDATOR in order to create an instance of A. The result is a performance hit because of an additional object creation and heap allocation.

process (a_value: detachable VALUE)
    local
        l_a: A
    do
        if (create {A_VALIDATOR}).is_valid_value (a_value) then
            create l_a.make (a_value)
            ...
        end
    end

Apart from the fragmentation and additional creation, it seems like the problem is solved. However, take the following adjustments, likely to transpire when a developer other than the original (maybe even the original developer) subclasses A to create a new type B:

class B
 
inherit 
    A redefine make, is_valid_value end
 
create
    make
 
feature {NONE} -- Initialization
 
    make (a_arg: detachable VALUE)
        do
            ...
        end
 
feature -- Query
 
    is_valid_value (a_value: detachable VALUE): BOOLEAN
            -- Determines if an argument is valid
        do
            Result := Precursor {A} (a_value) and then a_value.value.is_equal ("predefined")
        end
 
end

The effect isn't completely obvious. This is a common scenario and is actually a bug in B. The implementation does not follow the existing model of A, that is to say fragment and implement an additional class B_VALIDATOR. The real implementation needs to be:

class B_VALIDATION
 
inherit
    A_VALIDATOR redefine is_valid_value end
 
feature -- Query
 
    is_valid_value (a_value: detachable VALUE): BOOLEAN
            -- Determines if an argument is valid
        do
            Result := Precursor {A} (a_value) and then a_value.value.is_equal ("predefined")
        end
 
end

Now there has been a validation class defined, with implementation that actually belongs in B, B can now be defined as:

class B
 
inherit
    A redefine make, is_valid_value end
 
inherit {NONE}
    B_VALIDATION
 
create
    make
 
feature {NONE} -- Initialization
 
    make (a_arg: detachable VALUE)
        require
            a_arg_is_valid_value: is_valid_value (a_arg)
        do
            ...
        end
 
end

Only now can a client safely create an instance of B, but this time using B_VALIDATOR. So for a simple two class model there now exists four classes and an additional performance hit for the creation (or instance access of using a once function) of the validator instances.

Overuse of Inheritance

Due to the lack of an objectless routine mechanism in the Eiffel language there is tenancy to overuse inheritance because it is required. There is a need to use "shared" objects which expose a single instance of a class through a once function. If there was an objectless mechanism classes could just use the a objectless once function. The Eiffel compiler and EiffelStudio IDE make heavy use of shared classes, prefixed SHARED_. In a recent code review FEATURE_I inherits 24 parent classes, of which only 6 are actual affecting the model and functionality of the class. That is 18 classes inherited for shared access alone!

The use of inheritance causes another problem, performance degradation.

The Performance Problem

After indicating the problems with the object creation and overuse of inheritance, the issues of performance can be indicated with context.

The problem related to creation performance has already been indicated, doubling the object creation, or more conservatively the expense of an attribute access, just to query argument validity. The problem is exacerbated by making a dynamic call to the validation routine even though none is needed.

On that last remark, there are performance implications in using shared objects to access a singleton object. All the calls are on an instance, and so require the overhead to access the instance; in addition the calls cannot be optimized by the compiler and so are dynamically dispatched - far more expensive on performance than a single static call, which the Eiffel compiler could generate with the use of an objectless language addition.

A Proposal

In order to complete this document the author indicates a proposal for such a mechanism for permitting objectless calls.

Due to objectless calls being a language mechanism rather than a run-time attribute (such as a once's process/thread status) a new keyword is going to be required, minimizing possible conflicts with feature names used in existing systems. For this proposal the author suggests objectless, indicating a objectless call.

Objectless class should be permitted on functions, both regular and once functions. It is up to debate to indicate if procedures and class attributes are permitted to be objectless.

The objectless keyword should appear as a routine modifier, as with the frozen keyword. Just as the use of frozen is used to dictate to subclasses a routine cannot be redefined/undefined, objectless has a similar standing when being called, indicating (not dictating) to clients to use the static calling convention. Secondary to the argument, such placement is clear to a reader the call is objectless. The only alternative foreseeable placement for an objectless keyword would be a prior to a routine's type keyword (do, once, deferred, external), however in the case of the previous arguments of readability, it's the author's opinion the keyword should not appear there. There may be a suggestion to using a routine's note clause to indicate objectless status but this is a bad idea, not only for readability but because note clauses are informative structures that should not change routine convention.

class A
 
feature -- Access
 
    objectless f: STRING_8
            -- Proposal for an objectless function.
            -- This seems much more readable
        do
        end
 
    f: STRING_8
            -- Proposal for an objectless function, alternative style.
            -- Less readable as an objectless call
        objectless do
        end
 
end

Using objectless is an indication to client, use the static calling convention is permitted but is not necessary. Making the same call on an attached entity as a client will make the same call, as long as the entity is attached.

Solving the Creation Problem

To indicate how objectless calls can solve the issue highlighted in [#The_Creation_Problem The Creation Problem], consider the following code.

class A
 
create
    make
 
feature {NONE} -- Initialization
 
    make (a_arg: detachable VALUE)
        require
            a_arg_is_valid_value: is_valid_value (a_arg)
        do
            ...
        end
 
feature -- Query
 
    objectless is_valid_value (a_value: detachable VALUE): BOOLEAN
            -- Determines if an argument is valid
        do
            Result := a_value /= Void and then a_value.has_value
        ensure
            a_value_attached: Result implies a_value /= Void
            a_value_has_value: Result implies a_value.has_value
        end
 
end

For clients wanting to create an instance of A, the following code can now be written and guaranteed. There is no need for pulling up the feature is_valid_value into a separate validation class, or the creation of the validation class to perform prospective argument validation.

process (a_value: detachable VALUE)
        -- Processes a value node.
    local
        l_a: A
    do
        if {A}.is_valid_value (a_value) then
                -- The value is valid, so we can safely create an instance of {A}.
            create l_a.make (a_value)
            ...
        end
    end

Extensions

One extension to the proposal and possible advantage over languages with a similar mechanism is the possibility to redefine a objectless routine. The semantics of this have clear rules that govern the behavior of calls and the calling convention used. This extension requires consideration now because of possible impacts on user code in the future, if the language is to adopt the extension.

Unlike other languages, it should be possible to redefine any objectless routine.

For the purpose of explaining the semantics of redefinition, the following classes are used.

class A
 
feature -- Access
 
    objectless f: STRING do ... end
 
end
class B
 
inherit 
    A
 
end

Objectless Redefinition

An objectless routine may be redefined objectlessly with redefined functionality.

When used as a client, calling {A}.f will always take the version from A. When calling {B}.f the version from A will be called unless B redefines f:

class B
 
inherit 
    A redefine f end
 
feature -- Access
 
    objectless f: STRING do ... end
 
end

Still, when call {A}.f the version from A will be taken, but calling {B}.f will call the version from B as f has been redefined objectlessly.

Instance Redefinition

An objectless routine may be redefined to an instance routine but not the other way around. This is permitted because the original objectless routine requires no access to instance attributes or instance routines of the class. An instance redefined objectless routine may call the precursor safely in this respect.

class B
 
inherit 
    A
        redefine
            f
        end
 
feature -- Access
 
    f: STRING
        do
            Result := Precursor {A}.twin
            Result.as_lower
        end
 
end

In instance redefinition, when calling {A}.f, the version from A will be taken because a client called f using the static calling convention. However, attempting to call {B}.f will result in a compile-time error because f is not accessible statically, since it has been redefined as an instance routine.

Instance Call Semantics

When calling f on an attached entity, of type A or a subtype, the call semantics behave in the same way as every other call - using dynamic dispatch. If an instance of B is polymorphically assigned to an instance of A and B redefines f, objectlessly or otherwise, the call is made on the dynamic type of the attached entity, in this case B's version.

From within the confines of A, or a subclass of it, an an unqualified call to f the call should be treated in the same way as using an attached entity Current.f, that is, the call is dynamically dispatched. This behavior can be overridden using the static access calling convention. For example, when B redefines f, in A, it can be written {A}.f to ensure the version of f in A is called. Conversely in A calling f unqualified will dispatch the call, resulting in f's implementation in B being called.