12.2.1 Parameters, arguments, and return values

In Dylan, a function is called with zero or more arguments. The function can perform computations, which may have side effects. It then returns zero or more values to its caller. Each argument and each returned value is an object.

A function has zero or more parameters that determine the number and types of arguments that the function takes. Following is a simplified description of what happens when a function is called (for a generic function, this description applies to the method that it invokes):

1. An implicit body is entered. A body establishes the scope for all local variables bound inside the body.

2. The parameters are matched with the arguments to the function.

3. A local variable is created with the name of each parameter.

4. Each parameter — that is, each local variable with the name of a parameter — is initialized, or bound, to one of the arguments. (In some cases, the parameter is bound to a list of arguments, or to a default value.)

5. The code that makes up the actual body of the function is executed.

A function can have a value declaration that determines the number and types of values the function returns. If there is no explicit declaration, a default declaration allows the function to return any number of values of any type. Following is a simplified description of what happens when a function returns (for a generic function, this description applies to the method that it invokes):

1. The values returned by the last expression in the function's implicit body are matched with the values declared in the value declaration.

2. The function's implicit body is exited, ending the scope of all local variables (including parameters) established in that body.

3. The values specified by the value declaration are returned to the caller of the function. (Depending on the value declaration, the number of values returned to the function's caller might be more or less than the number of values returned by the last expression in the function's body.)

Note these two important implications of the way that arguments are passed:

define method calling-function ()
  let x = 1;
  let y = 2;
  format-out("In calling function, before call: x = %d, y = %d\n", 
             x, y);
  called-function(x, y);
  format-out("In calling function, after call: x = %d, y = %d\n", x, y);
end method calling-function;
define method called-function (x, y)
  x := 3;
  y := 4;
  format-out("In called function, before return: x = %d, y = %d\n", 
             x, y);
end method called-function;

A call to calling-function produces the following output:

In calling function, before call: x = 1, y = 2
In called function, before return: x = 3, y = 4
In calling function, after call: x = 1, y = 2
define class <test> (<object>)
  slot test-slot, required-init-keyword: test-slot:;
end class <test>;
define method calling-function ()
  let x = make(<test>, test-slot: "before");
  format-out("In calling function, before call: x.test-slot = %s\n",
             x.test-slot);
  called-function(x);
  format-out("In calling function, after call: x.test-slot = %s\n",
             x.test-slot);
end method calling-function;
define method called-function (x :: <test>)
  x.test-slot := "after";
  format-out("In called function, before return: x.test-slot = %s\n",
             x.test-slot);
end method called-function;

Note here that we have redefined the calling-function method, and have defined a new called-function method, which we first defined in the previous example. Our new called-function method has one parameter, whereas the previous method had two. The parameter list of this new method is not compatible with that of the previous method, and, if we actually tried to define the second called-function method, Dylan would signal an error. For more information on compatibility of parameter lists for generic functions and methods, see Section 12.2.5.

A call to calling-function now produces the following output:

In calling function, before call: x.test-slot = "before"
In called function, before return: x.test-slot = "after"
In calling function, after call: x.test-slot = "after"

In this case, x in the calling function and x in the called function are different variables. But the values of both variables are the same object: the instance of <test> that we make in the calling function. The change to the slot value of this object that we make in the called function is visible to the calling function.

It is equally proper to think of arguments that are immutable, like integers, as being shared between a function and its caller. By definition, however, a function cannot make any changes to such objects that are visible to the function's caller.

Comparison with C and C++: As in Dylan, the parameters of a C function are local to the body of the function, and assignment to a parameter does not affect the value of a variable that has the same name in the function's caller. But the relationship between objects and values is not the same in C and in Dylan. In C, a value can be an object (roughly meaning the contents of the object) or a pointer to an object (roughly meaning the location of the object in memory). The value of a parameter in C is always a copy of the corresponding argument. When a C structure is an argument to a function, the value of the corresponding parameter is a copy of the structure; it is not the structure itself. If the function changes the value of a member of this structure, the change is not visible to the caller, because the function is changing only its own copy of the structure. But if the argument is a pointer to a structure, the function can gain access to the caller's structure (by dereferencing the pointer). If the function changes the value of a member of such a structure by dereferencing the pointer, the change is visible to the caller.

In Dylan, a value is always an object, which has a unique identity. The value of a parameter is always the same object as the corresponding argument. When a function changes such an object (as by changing the value of a slot), the change is always visible to the caller. Dylan has no equivalent to C pointers.

In C++, a parameter declared using ordinary C syntax also receives a copy of a structure or an instance that is the corresponding argument. C++ has additional syntax for declaring that a parameter is a reference — essentially an implicit pointer — to the corresponding argument. In this case the argument is not copied, and if the function changes the object that the parameter refers to, the changes are visible to the caller. In some ways Dylan's argument-passing protocol is similar to C++ references.

In both C and C++, array arguments are always passed as pointers. In Dylan, arrays are instances of the <array> class, and array arguments are treated like all other arguments.

For more comparisons between Dylan and C objects, see Appendix B, Dylan Object Model for C and C++ Programmers.