Sunday, October 01, 2006

'in', 'inout' and 'out' parameters

This is an idea that I'm thinking about for some time. Hyper currently uses a parameter system that was borrowed from Oberon (I am not sure if its ancestors Modula-2 and Pascal already supported it) and Visual Basic. There are now 2 kinds of parameters: normal ones and variable parameters. Normal ones are passed by-value and variable ones are passed by reference. Now this would be a perfect parameter system if it was used for a language without pointers. An example from the Hyper docs on my website illustrates the problem:
procedure isCellEmpty(x & y : nat, m : * [ ] const * [ ] const * const int) : bool
return m[x, y] =$ null
end
So many const keywords just to make sure that no contents of the matrix are modified! Hyper needs a parameter to be declarable as 'input only'. Such a parameter would not need any const keywords, because for an input parameter it is obvious that no content can be modified:
procedure isCellEmpty(in x & y : nat, in m : * [ ] * [ ] * int) : bool
return m[x, y] =$ null
end
So a keyword in could be used for input parameters. The safest solution would make this behaviour the default for all parameters that don't specify another option. This would make the keyword in redundant. The other option would be an input/output parameter. It would pass by reference:
procedure makePositive(inout x : int)
if x < 0 then
x = -x
end if
end
And the const keyword can be used for data that is not supposed to be changeable:
# make sure that x points to the string to come first
procedure tinyAscendingSort(inout x & y : * const string)
if y < x then
var t : * const string = x
x =$ y
y =$ t
end if
end
Another useful feature would be 'output only' parameters. I am not sure whether or not to include them, because they would behave very differently from other parameter types. They would also use an implicit reference to pass through their changes. In my opinion an output parameter should be initialized by the function that assigns it a value. This requires a new statement, an 'out' statement. Such a statement assigns a value to an out parameter. Every return path of a procedure must have an out statement for each out parameter. Example:
procedure selectBestCandidate(c1 & c2 : * Candidate, out best : * const Candidate)
if c2.betterThan(c1) then
out best c2
else
out best c1
end if
end
Of course output parameters are best used for multiple output parameters, because otherwise you could just use the function's return value. Another example:
procedure getMinMaxAvg(i & j : int, out min & max : int, out average : real)
out min = (i < j) ? i : j
out max = (j < i) ? i : j
out average = (real(i) + real(j)) / 2
end
An out statement initializes its output parameter by using its constructor, so the specified value is like an intializer for a variable. We also need a special function call syntax for output parameters. This is because an output argument is not an expression but a variable declaration:
procedure p(x & y : int)
getMinMaxAvg(x, y, out smallest, out largest, out middle)
var diff : int = largest - smallest
end
The declaration of an output argument happens in an expression. To avoid dependencies on expression evaluation order, an output argument can olny be used after the entire expression (so that it isn't a subexpression of something else) is evaluated.

As said earlier, I am not really convinced about the current idea of output only parameters, but the input and input/output parameter ideas are good enough for me.

No comments: