-
The very first Ox program we saw,
- The address operator & again Recall from the chapter on addresses that
y = &A; println(y[0][2][3]," = ",A[2][3]);
In effect - 999 Arguments♭The O'Jays: Actual and Formal Arguments of a Function
20-arguments.ox 1: #include "oxstd.h" 2: cobb(x,y,aU); 3: 4: main() { 5: decl two = 2.0,u=0.0, ok; 6: ok = cobb(two,2.5,&u); //address of u 7: println("Output: ",ok," ",u); 8: } 9: 10: cobb(x,y,aU) { 11: if (x < 0 || y < 0 ) 12: return 0; 13: aU[0] = x^0.2 * y^0.8; 14: return 1; 15: } 16:
The writer of this program has defined a function named cobb, which apparently codes a two-dimensional Cobb-Douglas utility or production function. That is,
$$U(x,y) = x^{.2} y^{.8}.\nonumber$$
When Ox encounters - Arguments to Cobb() in the Program Above
Formal Actual Value Sent --------------------------------- x two 2.0 y 2.5 2.5
- There are both formal arguments and actual arguments.
- Formal arguments are placeholders: they are part of the form of the function. In Ox they appear in the function declaration and then can be used in the definition.
- Actual arguments are what goes in the placeholder at a particular point in run time. They are the items listed in a call to the function within the code.
- Each actual argument is matched to a formal argument by the order of the list inside
(…)
. - Visualizing Arguments Here we step through a program as a user-defined function is called, executes and returns. The value in memory related to formal and actual arguments are shown.
Seq Code ------------------------------- 2 cobb(x,y) { 3 return x^0.2 * y^0.8; 4 } 0 main() { decl two = 2.0,u; 1 u = cobb(two,2.5); 5 println("Output: ",u); }
Seq Code ------------------------------- 2 cobb(x,y,u) { 3 u = x^0.2 * y^0.8; 4 } 0 main() { decl two = 2.0, u = "hi"; 1 cobb(two,2.5,u); 5 println("Output: ",u); }
The problem: this program does not work!
- Get Me Outta Here When we combine pointers (in Ox, arrays) and pass-by-value functions we get a second way to get results out of a function. The key is that the program has to send a pointer to where it wants the results sent to. Recall from above, that
Seq Code ------------------------------- 2 cobb(x,y,u) { 3 u[0] = x^0.2 * y^0.8; 4 } 0 main() { decl two = 2.0,u; 1 cobb(two,2.5,&u); 5 println("Output: ",u); }
- Assigning Functions to Variables We have seen how to define functions and how to call them. It is possible for an Ox variable to contain a function to be called. This also allows a function to be passed as an argument to other functions, among other uses. Here is some code that shows the idea:
linear(x) { return alpha*x[0]+(1-alpha)*x[1]; } cobb_douglas(x) { return x[0]^alpha*x[1]^(1-alpha); } decl util; if (utype==0) util = linear; else util = cobb_douglas;
So apparently the variable - Default Actual Arguments Some built in Ox functions have optional arguments. For example, the
decl A = < 1,0 ; -1,3 >; println("A inverse=",invert(A));
There is another form of the same function as explained in its (simplified) Ox documentation entry:
invert(ma); invert(ma, alogdet, asign); ma in: m x m real matrix A alogdet in: (optional argument) address of variable out: double, the logarithm of the absolute value of the determinant of A asign in: (optional argument) address of variable out: int , the sign of the determinant of A; 0: singular; -1,-2: negative determinant; +1,+2: positive determinant; -2,+2: result is unreliable
You could also use it this way:
decl A = < 1,0 ; -1,3 >, B, ldet; B = invert(A,&ldet); println("A inverse=",B,"log determinant=",ldet);
Suppose you wanted to write your own function with this feature. You could do this by providing default actual arguments for arguments that are optional. In particular, myinvert(a,ld=0,sgn=0); //declare the default value of arguments myinvert(a,ld,sgn) //define the function { decl ainv; if (isint(ld)) // no address sent, ld is an integer return invert(a); if (isint(sgn)) // address sent for ld but not sgn return invert(a,ld); return invert(a,ld,sgn); // addresses sent for both }
Now you can make calls to myinvert(A); is the same as myinvert(A,0,0); myinvert(A,&ldet); is the same as myinvert(A,&ldet,0); myinvert(A,&ldet,&sng); is the same as myinvert(A,&ldet,&sng);
In other words, if any optional actual argument is not sent Ox will insert the default value in its place. In this case, the default values are the integers 0. So if - Different Kinds of Arguments Here are the declarations of 4 different functions that accept different kinds of arguments:
Declaration Explanation and Example Calls
f0() no arguments: f0() is the only valid call f1(x) 1 argument: f1("a") f1(0.0) f1(x) f2(x,y) 2 arguments: f2(n,m) f2(0.0,-3.0) f2("hi",{"bye"}) g1(x=1.0) 1 optional argument: g1() g1(5.0) g2(x,y=0.0) 1 required, 1 optional: g2(2.0) g2(v,-2.0) gn(x,...) 1 required, variable list: gn(5) gn(5,"a","b","c"); g0(...) none required, variable- Anonymous Functions Like Python and other languages, Ox has anonymous or "lambda" functions. However, we will not use these so the student should refer to the Ox documentation for explanation.
01a-hello-world.ox
, included the built-in function println()
. Hopefully the role of functions like that in was intuitive enough that basic functions could be used without a lot of explanation. However, to do more in Ox and similar languages requires some explanation about how functions work. This is especially true once you begin to write your own functions.
&
is the address operator in Ox. That is, &x
is the memory location of the variable x
. The operand x
must be what Ox documentation calls an lvalue. In the terminology of these notes it has to be a
thing. It is possible to take the address of a class object, a function, or an array element, but not of a matrix element.
So you cannot code y = &A[2][3]
so that y
points at the 2,3 element of a matrix say. But you can write y = &A
which means y
points to the variable A
which might currently contain a matrix. Geek Talk
The reason for these restrictions have to do with the "under-the-hood" features of Ox as a language executed by a C program and discussed in the chapter on Binding. For readers who know more about these things, an Ox pointer is really a C pointer to an oxvalue
discussed in that chapter. Since an element of a matrix is simply a floating point real inside a C array it is not an oxvalue
so it cannot be pointed to by an Ox pointer. Only if you were rewriting the C program that runs Ox code could you have a pointer to a specific element of a matrix.]
If you can't point directly to A[2][3]
, how could you use y
to get at it? This is a somewhat tricky aspect of Ox. Essentially we need to undo the address operator &
. We want to access the thing y
is pointing to. What makes it tricky is that this looks like accessing an element of a matrix. That is, y[0]
means Look at the content of
y
. It is pointing to another thing. Go to that thing.
y[0]
is the same as A
if y
contains a pointer to (or the address of) A
.
In an expression, f()
calls the function f
. That is, whatever that function does will happen. In that case it appears f
has no arguments or inputs. If it does want information from the program then the values are listed inside the parentheses. We saw this immediately in 01a-hello-world.ox
because that program contained the statement println("hello, world");
. In other words, the string hello, world
was sent or passed to the function println
.
Information passing inside a program can be confusing even if you have programmed before, because languages differ on how to pass information. In Ox all arguments are pass-by-value. To explain what that means we will work with this simple program:
cobb(x,y)
it means the programmer is declaring a function named cobb
that takes two arguments. That means to Ox that this program is going to include calls to such a function so you should presume it exists. The material between the curly brackets that follow, { … }
, is the definition of the function. It is what will happen when cobb()
is called in the program.
In this case the declaration and definition are in the same place. However, it is possible to separate the two. Just uncomment the second line of that file and run it again. In that version the declaration of cobb()
is separate from its definition
. Notice that the definition now repeats the function form, cobb(x,y)
. This separation can be very important in large and complex programs, because it allows a program to be processed (compiled) with just the functions used in it declared. The definitions can be brought in later when the program is linked and executed.
In the definition of cobb()
the symbols x
and y
are called its formal arguments. They are the names for the two things that cobb
wants the program to send to it. What is listed in a call to the function are the actual arguments. Every time a function is called new actual arguments are sent. Inside the function they are used or referred to by the formal arguments. The matching up of actual arguments is in ordering. So x
is the first formal argument of cobb
and thus is replaced by the first actual argument, in this case two
.
Ox is a pass-by-value language. That means the value of the actual argument is sent to the function, not the argument itself. So all arguments are evaluated before the function is entered. When an array is passed, its contents may be changed by the function (unless they are const
).
To summarize, the term argument
refers to an item listed in parentheses after a function name.
Above, cobb()
is called on line 7. The first value sent to it is two
, a local variable inside main()
. In Ox, the value of the actual argument is sent to the function and is available to the function in the corresponding formal arguments. So two
is paired with the formal argument x
at that call to the function. The value that x
holds at the start of the function is the value of two
or the integer 2. Python is a pass-by-reference language. That means if you are used to Python you will find Ox arguments confusing because they act differently when the function changes the argument.
The numbers are obviously not the line numbers in the code. Instead, they are the order in which lines are executed as the program runs. Remember that execution always starts in main()
, and many things have been set up by the time that line is reached. We call that time 0 or RT0. Then the value of memory cells is tracked at the point the function is called (time 1). Then we move to the start of cobb()
, step 2 before any of the statements in {…}
happen. Time 4 is associated with }
, which is the point when the return value has been set and control is returning back to the function call. Finally, time 5 is after the assignment has occurred and the function call is completed.
Exhibit 21. Getting Results out in the return
value of a function
Address | Label | Contents By Sequence | |||||
---|---|---|---|---|---|---|---|
0. | 1. | 2. | 3. | 4. | 5. | ||
0500 | anonymous | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 |
0501 | anonymous | 2.5 | 2.5 | 2.5 | 2.5 | 2.5 | 2.5 |
0A23 | main::two | ?? | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 |
0A24 | main::u | ?? | ?? | ?? | ?? | ?? | 2.3909 |
2B00 | cobb::return | ?? | ?? | 0 | 0 | 2.3909 | ?? |
2B8C | cobb::x | ?? | ?? | 2.0 | 2.0 | 2.0 | ?? |
2E8D | cobb::y | ?? | ?? | 2.5 | 2.5 | 2.5 | ?? |
cobb()
to return the utility of the consumption bundle $(x,y)$ in a third argument rather than as the return value of the function:
Exhibit 22. Results Can't Be Passed Back in Variables Sent as Formal Arguments
Address | Label | Contents By Sequence | |||||
---|---|---|---|---|---|---|---|
0. | 1. | 2. | 3. | 4. | 5. | ||
0500 | anonymous | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 |
0501 | anonymous | 2.5 | 2.5 | 2.5 | 2.5 | 2.5 | 2.5 |
0A23 | main::two | ?? | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 |
0A24 | main::u | ?? | hi | hi | hi | hi | hi |
2B00 | cobb::return | ?? | ?? | 0 | 0 | 0 | ?? |
2B8C | cobb::x | ?? | ?? | 2.0 | 2.0 | 2.0 | ?? |
2E8D | cobb::y | ?? | ?? | 2.5 | 2.5 | 2.5 | ?? |
2B8E | cobb::u | ?? | ?? | hi | hi | 2.3909 | ?? |
We can see that there are two different memory cells with the label u
. That's because u
appears in two different decl
statements. One belongs to main()
and one belongs to cobb()
. They are not the same thing so they occupy different places in memory. Because Ox is pass-by-value the assigned value of utility cannot get out of cobb()
to be stored as the value of main::u
.
This might appear to mean that the only way to get results out of a function is through return
. But there is a way: send as the actual argument the address of the location you want results to go to.
&
is the location-ofoperator.
Exhibit 23. Send Results Back Through a Pointer Argument
Address | Label | Contents By Sequence | |||||
---|---|---|---|---|---|---|---|
0. | 1. | 2. | 3. | 4. | 5. | ||
0500 | anonymous | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 |
0501 | anonymous | 2.5 | 2.5 | 2.5 | 2.5 | 2.5 | 2.5 |
0A23 | main::two | ?? | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 |
0A24 | main::u | ?? | ?? | ?? | ?? | 2.3909 | 2.3909 |
2B00 | cobb::return | ?? | ?? | 0 | 0 | 0 | ?? |
2B8C | cobb::x | ?? | ?? | 2.0 | 2.0 | 2.0 | ?? |
2E8D | cobb::y | ?? | ?? | 2.5 | 2.5 | 2.5 | ?? |
2B8E | cobb::u | ?? | ?? | 0A24 | hi | 2.3909 | ?? |
utype
holds which type of utility function to use each time the program runs. It could be Cobb-Douglas or linear. Now the rest of the code can contain util(x)
to call the appropriate utility. We will see uses of this later on.
invert()
function will compute the inverse of a matrix, so it could be used like this:
myinvert()
could be a user defined function that looks like this:
myinvert()
of these forms:
myinvert()
gets an integer value in the second argument it knows the user did not want to get the log-determinant. You cannot do this: myinvert(A,,&sng)
. That is, you cannot skip one actual argument and have the default argument sent for it but send your own argument for those to the right.
Notice that optional arguments have to be listed after any required arguments. It is not possible to declare h(x=2.0,y)
because the call h(3.0)
changes the default value of x but does set the values of the required argument y.
The default argument and variable list "..." only appear in the function declaration. They do not appear when you call the function. And if the declaration and definition are separated the definition simply lists the arguments without defaults.
Python and Java have "keyword arguments" but Ox does not allow this.
Exercises
return
statement. What happens if a return value is expected but not is set? Find out for yourself by running this program:#include "oxstd.h" sayhello() { println("hello"); } main() { decl y; y = sayhello(); println("sayhello returns ",y); }
print() scan()Modify programs already using these functions to print out the return value of the functions and confirm them.
matadd(A,B,aSum) A, B n x m matrices aSum input address, output n x m matrix A + B, if well-defined returns 1 if A + B is well-defined; 0 if not well-defined.
trans(A) A input matrix return A' transpose of A
logkernel(x) x n x 1 vector return the n x 1 vector logistic transformation of vInclude a main() routine that demonstrates your function. Save the output it produces to a file.