So far we have talked about how intended functions describe the semantics of a prime — what the code does. However, your code may not be able to guarantee all the conditions necessary for successful operation. This means that you may specify preconditions in order shift the burden of certain conditions onto the caller.
Let's take for example a function
that computes the square root. Here's our first attempt:
# [ y := square root of x ] y = math.sqrt(x)
However, what if the caller passes a negative argument?
Worse yet, what if the caller passes a string argument?
x to be a number!
The way to handle this is to specify one or more
preconditions. A precondition is a statement that must
be true for the code to work properly. Here's our
revised intended function for
# [ x is a nonnegative number -> # y := square root of x ]
Another name for this convention is partial
specification. We define what our function
x is a nonnegative number, but we
don't define what it does for any other cases. The
precondition precedes the rest of the intended function,
followed by “
Another way to read this intended function is as a contract:
If the caller insures that all preconditions are true, the prime guarantees to make the specified changes in state items.
Recall Stan Kelly-Bootle's definition of an interface in
Section 2, “The contract-based approach to program
construction”? With our improved intended
function, we can apportion the blame for malfunctions
quite clearly. If the caller supplied a nonnegative
number and didn't get the right result, it's the fault of
sqrt function. If the caller
didn't supply a nonnegative
number, it's the caller's fault if
the result is wrong.
Here's an example of multiple preconditions: an intended
function for the
atan2(y,x) function in
the standard Python
# [ (y is a number) and (x is a number) -> # return the angle that a vector from the origin to # (x,y) makes relative to the x-axis, in radians, in # the range [-pi, pi] ]
What about functions and methods with optional arguments?
Here's an example intended function to describe the
s.rjust(n[, fill]) method of the Python
# [ (s is a string) and (n is a positive int) and # (fill is a string) -> # return s, left-padded to length n with fill (defaulting # to spaces) ]
There are other ways to write this intended function.
For example, you could write the function signature as
s.rjust(n, fill=' ')”, in
which case the intended function wouldn't even have to
mention the default value process because it is built
into the Python semantics for function calls: if you
leave out the second argument to
it's the same as if you passed a space for that argument.