Output arguments

by Helmut Brandl (modified: 2010 Oct 26)

In Eiffel all arguments of routines are pure input arguments. There is no possibility to return an argument. The only possibility is to write a function and return a value as "Result".

Furthermore Eiffel has command query separation, i.e. a command can change states and a query should only return a result and do nothing beside that. Command query separation -- allthough not yet enforced by the language and its compilers -- is a very powerful software principle which allows us to reason about our software and use queries within assertions.

However there are routines which are commands (because they change state) and have a result. E.g. the feature `read_character' of class FILE advances the cursor of the file (therefore it changes state and is a command), but has as a result the read character. The only possibility in Eiffel is currently to store the read character in the file object and provide a query `last_character' which returns the last read character.

The disadvantages are:

- bad performance (because the runtime stack or registers cannot be used to transport the results)

- blown up objects (each file has a last_character', last_real', last_integer', last_natural', `last_string', etc.)

For these cases it would be good to have output arguments by keeping the distinction between commands and queries.

E.g. we could invent a mechanism which allowed us to write

ch: CHARACTER ... file.read_character(ch)

with the effect of reading a character from `file' and store the result in the local variable (or attribute) `ch. This is common practice in many programming languages. However it contradicts the Eiffel principle that everything shall be as explicit and readable as possible. The reader of `file.read_character(ch) cannot see immediately whether `ch' is an input or an output argument. He would have to look into the definition of `read_character' to see that. Therefore some syntactic sugar is useful to make that distinction, e.g. the invocation of the command can be

file.read_character -> ch

and the feature can be declared as

read_character (-> ch: CHARACTER) -- read one character from the file and store it into `ch' require not end_of_file ... end

In general the definition of a command could be

some_command(i1:I1; i2:I2; ... -> o1:O1; o2:O2; ...)

and its general use

tgt.some_command(i1,i2,...) -> o1,o2,...

Validity rules:

- A function cannot have output arguments (only Result)

- Output arguments must be initialized properly

- Output arguments can occur in postconditions, but not in preconditions.

Advantages:

- strict command query separation

- leaner objects

- higher performance

- good expliciteness and readability

Comments
  • David Le Bansais (14 years ago 28/10/2010)

    In your example

    In your example

    read_character (-> ch: CHARACTER) -- read one character from the file and store it into `ch' require not end_of_file ... end

    Do you have the value of ch stored in the object upon return? (In last_character for instance). If not, can't you obtain the same behavior with a function returning a TUPLE result containing all values o1, o2... ?

    • Helmut Brandl (14 years ago 30/10/2010)

      command query separation

      A function returning a TUPLE breaks command query separation. If you do that, you can use the function within an assertion and then you have an assertion with side effects. This shall never happen.

  • Berend de Boer (14 years ago 28/10/2010)

    This post would be stronger if you actually could have come up with a single example that last_character and such is slower. It isn't. Trust me.

    Come up with a non-trivial piece of code and the Eiffel way is as fast as C if not faster.