On occasion, we will encounter problems that require functions on two
complex classes of inputs. The most interesting situation occurs when both
inputs are of unknown size. As we have seen in the three first
subsections, we may have to deal with such functions in three different
ways.
The proper approach to this problem is to follow the general design
recipe. In particular, we must conduct a data analysis and we must define
the relevant classes of data. Then we can state the contract and the
purpose of the function, which, in turn, puts us into a position where we
can think ahead. Before we continue from this point, we should decide
which one of the following three situations we are facing:
- In some cases, one of the parameters plays a dominant
role. Conversely, we can think of one of the parameters as an atomic piece
of data as far as the function is concerned.
- In some other cases, the two parameters are synchronized. They must
range over the same class of values, and they must have the same
structure. For example, if we are given two lists, they must have the same
length. If we are given two Web pages, they must have the same length, and
where one of them contains an embedded page, the other one does, too. If
we decide that the two parameters have this equal status and must be
processed in a synchronized manner, then we can pick one of them and
organize the function around it.
- Finally, in some rare cases, there may not be any obvious connection
between the two parameters. In this case, we must analyze all possible
cases before we pick examples and design the template.
For the first two cases, we use an existing design recipe. The last case
deserves some special consideration.
After we have decided that a function falls into the third category and
still before we develop examples and the function template, we develop a
two-dimensional table. Here is the table for <#64283#><#21051#>list-pick<#21051#><#64283#> again:
#tabular21053#
Along the horizontal direction we enumerate the conditions that
recognize the subclasses for the first parameter, and along the vertical
direction we enumerate the conditions for the second parameter.
The table guides the development of both the set of function examples and
the function template. As far as the examples are concerned, they must
cover all possible cases. That is, there must be at least one example for
each cell in the table.
As far as the template is concerned, it must have one <#64290#><#21074#>cond<#21074#><#64290#>-clause
per cell. Each <#64291#><#21075#>cond<#21075#><#64291#>-clause, in turn, must contain all feasible
selector expressions for both parameters. If one of the parameters is
atomic, there is no need for a selector expression. Finally, instead of a
single natural recursion, we might have several. For <#64292#><#21076#>list-pick<#21076#><#64292#>,
we discovered three cases. In general, all possible combinations of
selector expressions are candidates for a natural recursion. Because we
can't know which ones are necessary and which ones aren't, we write them
all down and pick the proper ones for the actual function definition.
In summary, the design of multi-parameter functions is just a variation on
the old design-recipe theme. The key idea is to translate the data
definitions into a table that shows all feasible and interesting
combinations. The development of function examples and the template
exploit the table as much as possible. Filling in the gaps in the template
takes practice, just as with everything else.