Author:halw

Date:2008-12-18T21:38:10.000000Z


git-svn-id: https://svn.eiffel.com/eiffel-org/trunk@141 abb3cda0-5349-4a8f-a601-0c33ac3a8c38
This commit is contained in:
halw
2008-12-18 21:38:10 +00:00
parent c49d647159
commit aa6644170d

View File

@@ -31,21 +31,21 @@ When you use an agent from a client routine, you will be building an instance of
Below is an instruction which passes an agent as an argument to a procedure.
<code>
button.select_actions.extend (agent gauge.step_forward)
button.select_actions.extend (agent gauge.step_forward)
</code>
In this example, the producer wants to add the action of stepping the gauge forward in the event that a button is clicked. The keyword " <code>agent</code>" is used to indicate that at runtime an object of type <code>PROCEDURE</code> should be created which represents applying the feature <code>step_forward</code> to the object attached to <code>gauge</code>. It is the object of type <code>PROCEDURE</code> that is passed as the argument.
In this example, the producer wants to add the action of stepping the gauge forward in the event that a button is clicked. The keyword "<code>agent</code>" is used to indicate that at runtime an object of type <code>PROCEDURE</code> should be created which represents applying the feature <code>step_forward</code> to the object attached to <code>gauge</code>. It is the object of type <code>PROCEDURE</code> that is passed as the argument.
It is important to understand that <code>step_forward</code> does not get applied at the point that the instruction above is executed. Rather the procedure object that represents <code>step_forward</code> is given to the button to hold in reserve. Then at the point that the button click event takes place, the button will go through its list of <code>select_actions</code> executing their associated routines. Only then does <code>step_forward</code>get applied to <code>gauge</code>.
===Agents with Arguments===
In this example, the routine " <code>step_forward</code>" on which the agent is based takes no arguments. If you drilled down into the workings of this example you would find that class that implements the feature <code>extend</code> is class <code>EV_NOTIFY_ACTION_SEQUENCE</code>. You would also see that the signature for the feature <code>extend</code> is as essentially as follows.
In this example, the routine "<code>step_forward</code>" on which the agent is based takes no arguments. If you drilled down into the workings of this example you would find that class that implements the feature <code>extend</code> is class <code>EV_NOTIFY_ACTION_SEQUENCE</code>. You would also see that the signature for the feature <code>extend</code> is as essentially as follows.
<code>
extend (v: PROCEDURE [ANY, TUPLE])
extend (v: PROCEDURE [ANY, TUPLE])
</code>
We don't have to know too much about the workings of agents to see that " <code>extend</code>" takes an argument <code>v</code> which is of type <code>PROCEDURE</code>. It turns out that the actual generic parameter <code>TUPLE</code> represents the set of "open" arguments. In this case, extend is expecting an agent which has no open arguments.
We don't have to know too much about the workings of agents to see that "<code>extend</code>" takes an argument <code>v</code> which is of type <code>PROCEDURE</code>. It turns out that the actual generic parameter <code>TUPLE</code> represents the set of "open" arguments. In this case, extend is expecting an agent which has no open arguments.
===Open and Closed Arguments===
@@ -53,117 +53,117 @@ It is this business of open and closed arguments which really makes agents remar
Suppose a class has a feature declared as shown below.
<code>
my_procedure: PROCEDURE [ANY, TUPLE]
my_procedure: PROCEDURE [ANY, TUPLE]
</code>
Then what can be assigned to <code>my_procedure</code>?. An agent, of course. Say the class has procedures as follows.
<code>
no_argument_procedure is
-- A procedure with no arguments
do
print ("No argument here!%N")
end
no_argument_procedure
-- A procedure with no arguments
do
print ("No argument here!%N")
end
two_argument_procedure (an_int: INTEGER; another_int: INTEGER) is
-- A procedure with two arguments
do
print ("My arguments are: " + an_int.out + " and " + another_int.out + "%N")
end
two_argument_procedure (an_int: INTEGER; another_int: INTEGER)
-- A procedure with two arguments
do
print ("My arguments are: " + an_int.out + " and " + another_int.out + "%N")
end
</code>
Then the following assignment is valid.
<code>
my_procedure := agent no_argument_procedure
my_procedure := agent no_argument_procedure
</code>
What this means is that the agent created and associated with the procedure <code>no_argument_procedure</code> must conform to the type <code>PROCEDURE</code> [ <code>ANY</code>, <code>TUPLE</code>]. The feature <code>my_procedure</code> (which is of type <code>PROCEDURE</code>) can be attached at runtime to an agent representing a procedure with no open arguments, which indeed is what <code>no_argument_procedure</code> is.
What this means is that the agent created and associated with the procedure <code>no_argument_procedure</code> must conform to the type <code>PROCEDURE [ANY, TUPLE]</code>. The feature <code>my_procedure</code> (which is of type <code>PROCEDURE [ANY, TUPLE]</code>) can be attached at runtime to an agent representing a procedure with no open arguments, which indeed is what <code>no_argument_procedure</code> is.
Now let's turn our attention to the other procedure <code>two_argument_procedure</code>. You might think that because it takes two arguments, that you would not be able to build an agent from it which could be assigned to the attribute <code>my_procedure</code>. But you can do it by closing the two arguments at the time that the agent is created, as in the following.
<code>
my_procedure := agent two_argument_procedure (1, 2) -- Is Valid
my_procedure := agent two_argument_procedure (1, 2) -- Is Valid
</code>
What happens here is that values are fixed for those arguments at the time that the agent, an object of type <code>PROCEDURE</code> [ <code>ANY</code>, <code>TUPLE</code>] is created.
What happens here is that values are fixed for those arguments at the time that the agent, an object of type <code>PROCEDURE [ ANY, TUPLE]</code> is created.
So this is the wonderful thing about agents. A routine which will be represented as an agent does not have to be an exact fit for the expected signature. By closing some arguments at agent creation, you have effectively produced a new and conforming routine.
The advantange of this is that you can sometimes avoid building specialized routines for the sole purpose of having a routine which conforms to the agent signature.
The advantage of this is that you can sometimes avoid building specialized routines for the sole purpose of having a routine which conforms to the agent signature.
To leave an argument open, you hold its place with a question mark. If you intend for all arguments to be open, then you may make them all question marks, or leave off the arguments entirely.
<code>
my_procedure := agent two_argument_procedure (?, 2) -- Argument 1 left open
my_procedure := agent two_argument_procedure (?, ?) -- Both arguments left open
my_procedure := agent two_argument_procedure -- Both arguments left open
my_procedure := agent two_argument_procedure (?, 2) -- Argument 1 left open
my_procedure := agent two_argument_procedure (?, ?) -- Both arguments left open
my_procedure := agent two_argument_procedure -- Both arguments left open
</code>
If an argument is open, then it means that a value is not provided for that argument at the time that the agent is created. The implication is that the value must be provided at some time prior to the time that the agent's associated routine gets executed. A precondition to executing a routine associated with an agent is that the agent has a valid set of arguments (called operands within the <code>ROUTINE</code> classes) for the call. If you were to leave one or both of the arguments to <code>two_argument_procedure</code> open as in the examples above, the assignment would still work due to the rules governing <code>TUPLE</code> conformance. But, at runtime unless the other arguments had been provided, the " <code>valid operands</code>" precondition would be violated.
If an argument is open, then it means that a value is not provided for that argument at the time that the agent is created. The implication is that the value must be provided at some time prior to the time that the agent's associated routine gets executed. A precondition to executing a routine associated with an agent is that the agent has a valid set of arguments (called operands within the <code>ROUTINE</code> classes) for the call. If you were to leave one or both of the arguments to <code>two_argument_procedure</code> open as in the examples above, the assignment would still work due to the rules governing <code>TUPLE</code> conformance. But, at runtime unless the other arguments had been provided, the "<code>valid operands</code>" precondition would be violated.
Let's see an example in which we leave a target open. Suppose we have a class that has a feature coded as below
<code>
my_strings: LINKED_LIST [STRING]
my_strings: LINKED_LIST [STRING]
</code>
and some code to put some strings in <code>my_strings</code>:
<code>
create my_things.make
my_strings.extend ("Hello")
my_strings.extend ("World!")
create my_things.make
my_strings.extend ("Hello")
my_strings.extend ("World!")
</code>
Our class also has a feature called <code>print_on_new_line</code> which we created to print a string preceded by a new line character.
<code>
print_on_new_line (s: STRING) is
-- Print `s' preceded by a new line
do
print ("%N" + s)
end
print_on_new_line (s: STRING)
-- Print `s' preceded by a new line
do
print ("%N" + s)
end
</code>
Now suppose we want to print the values of all the strings in <code>my_strings</code> each on a separate line by invoking <code>print_on_new_line</code>. Traditionally, we would do it by traversing the <code>LINKED_LIST</code> and printing each item. Like this:
<code>
from
my_list.start
until
my_list.exhausted
loop
print_on_new_line (my_list.item)
my_list.forth
end
from
my_list.start
until
my_list.exhausted
loop
print_on_new_line (my_list.item)
my_list.forth
end
</code>
The availability of agents gives us new options. <code>LINKED_LIST</code> has a feature <code>do_all</code> which comes to it from its ancestor <code>LINEAR</code>. The <code>do_all</code> feature's signature looks like this:
<code>
do_all (action: PROCEDURE [ANY, TUPLE [G]])
do_all (action: PROCEDURE [ANY, TUPLE [G]])
</code>
As an argument <code>do_all</code> takes an agent based on a procedure with one open argument which is the same type as the list items (in this class, <code>G</code> is the formal generic parameter representing the type of the items being stored). Then it traverses the list executing the routine associated with that agent and roviding the current list item to satisfy the open argument.
As an argument <code>do_all</code> takes an agent based on a procedure with one open argument which is the same type as the list items (in this class, <code>G</code> is the formal generic parameter representing the type of the items being stored). Then it traverses the list executing the routine associated with that agent and uses the current list item to satisfy the open argument.
Instead of coding the loop shown above, we can code this instruction:
<code>
my_list.do_all (agent print_on_new_line (?))
my_list.do_all (agent print_on_new_line (?))
</code>
we leave the argument to <code>print</code> open, and <code>do_all</code> will provide it as a reference to the current list item as it traverses the list.
we leave open the argument required by <code>print</code>, and <code>do_all</code> will provide it as a reference to the current list item as it traverses the list.
===Targets for Agents' Routines===
In Eiffel every routine must be applied against a target object. In our model for computation, <code>x</code>. <code>f</code> ( <code>a</code>, ...), <code>x</code> is the target of the application of feature <code>f</code>. In the case of an agent, the agent must account for objects for each of the arguments and an object for the target of the routine.
In Eiffel every routine must be applied against a target object. In our model for computation, <code>x.f (a, ...)</code>, the <code>x</code> is the target of the application of feature <code>f</code>. In the case of an agent, the agent must account for objects for each of the arguments and an object for the target of the routine.
Let's identify the targets in the examples shown. First:
<code>
button.select_actions.extend (agent gauge.step_forward)
button.select_actions.extend (agent gauge.step_forward)
</code>
Here the target is the object attached to the entity "gauge" which is (although you cannot determine it from this line taken out of context) an object of type <code>EV_GAUGE</code>.
How about this:
<code>
my_procedure := agent two_argument_procedure (1, 2)
my_procedure := agent two_argument_procedure (1, 2)
</code>
Here, since there was no qualification, then the target is the current instance. Same with this:
<code>
my_list.do_all (agent print_on_new_line (?))
my_list.do_all (agent print_on_new_line (?))
</code>
Again, consider the fact that the agent must account for objects for each of the arguments to a routine, and an object for the target. So, in the examples we've seen so far, the target is close, that is provided at the time of the creation of the agent.
@@ -172,9 +172,9 @@ But we can actually leave the target open as well. Now we cannot use the questio
Suppose in our list of strings example, we wanted to print the strings, then convert them to lower case, then print them again. Remember that "do_all" has one open argument, which will be provided as the current list item during the traversal.
<code>
my_list.do_all (agent print_on_new_line (?))
my_list.do_all (agent {STRING}.to_lower)
my_list.do_all (agent print_on_new_line (?))
my_list.do_all (agent print_on_new_line (?))
my_list.do_all (agent {STRING}.to_lower)
my_list.do_all (agent print_on_new_line (?))
</code>
In between printing the list two times, we provide <code>do_all</code> with an agent that representing the <code>STRING</code> class's feature <code>to_lower</code> which will convert each string in the list to lower case. Notice that <code>to_lower</code> does not take an argument of type <code>STRING</code> as <code>print_on_new_line</code> did. Rather it gets applied to an instance of <code>STRING</code>, so it is targeted to a string. So we leave its target open and <code>do_all</code> provides the current list item as the target.
@@ -187,7 +187,7 @@ Let's extend the string example by using an agent that represents a function. Su
Here again we'll use a feature of the <code>LINKED_LIST</code> class. There is a feature called <code>do_if</code> which takes two agents as arguments. One is an action procedure like the argument that <code>do_all</code> takes, and the other is a function which returns a boolean and used as a test. As each list item is current, the test is applied first. If the result is true, then the action is applied with the current item.
<code>
my_list.do_if (agent print_on_new_line(?), agent {STRING}.has('!'))
my_list.do_if (agent print_on_new_line(?), agent {STRING}.has('!'))
</code>
The agent for the action is the same as we used earlier. We've added an agent for the test. It represents applying the <code>has</code> feature of the <code>STRING</code> class. Here the target is left open, because we want each of the strings in the list to be the target of <code>has</code>.