A higher-order function is simply a function that accepts other functions as arguments. As presented in Chapter 6, Rosetta functions are first-class items having labels, types, and values like any other declared item. Thus, it is possible to define a higher-order function simply by including a parameter in with a function type in a function’s signature. Consider the simple example of a selection function for choosing between demodulation schemes in a receiver:
demod(dem1,dem2::< *(s::real)::real*>; modType::boolean; x::real)::real is if modType then dem1(x) else dem2(x) end if;
In this function, x
is the input signal that will be demodulated and modType
indicates what modulation type to select. The parameters dem1
and dem2
are functions that map real
values to real
values and represent the two alternative demodulation schemes. When evaluated, the function uses its boolean
input to select from the two demodulation schemes. The advantage to this approach is that demodulation types need not be known when the function is developed. The designer may select a general architecture for the design while deferring specifics until a later point in the design process process. The demodulation function can be instantiated as follows:
demod(qpsk,vsb,true,v)
where v
is the input signal and qpsk
and vsb
are functions that perform demodulation.
The approach becomes more powerful when combined with curried function evaluation. Using curried function evaluation, it is necessary to provide values for only a subset of a function parameters. The following definition shows an example of defining a specific demodulation paradigm using the functions defined above:
demod1::<*(modType::boolean;x::real)::real*> is
demod(qpsk,vsb);
When evaluated, demod(qpsk, vsb)
results in a new function that evaluates an input signal with either qpsk(x)
or vsb(x)
, depending on the boolean select value, modType
. This resulting function is used as the value for function demod1
. The demod
function provides an architecture for demodulation functions that can be instantiated for specific situations.
We can take the example one step farther and instantiate demod1
to be a qpsk
demodulator exclusively:
qpsk1::<*(x::real)::real*> is demod1(true);
Most applications of higher-order functions are transparent to the Rosetta user. Specification functions such as quantifiers, set operations, and map and filter operations represent applications of higher-order functions that are used without fanfare in the base language. However, the higher-order technique is available to users and represents a powerful specification technique.
The core of Rosetta’s built-in higher-order functions includes functions for extracting the domain, range, and return type from a function definition. Described in Table 7.1, dom
, ran
, and ret
provide capabilities for extracting domain, range, and return type from a function, respectively. The dom
function accepts a function as its only argument and returns the domain of that function. For unary functions such as inc
, this is simply a process of returning the type associated with the input parameter. If inc
is defined in the traditional fashion:
inc(x::natural)::natural is x+1;
Table 7.1. Higher-order functions for accessing properties of function definitions
Operation | Format | Meaning |
---|---|---|
Domain |
| Domain of a function |
Range |
| Range of a function |
Return Type |
| Return type of a function |
the domain of inc
is found by using the dom
function:
dom(inc) == natural;
ret
is the return type of the function. Using inc
again as an example, the return type is found by evaluating ret
on the function:
ret(inc) == natural;
Like the domain function, the return type function is realized by simply examining the function definition.
The ran
function accepts a single, arbitrary function as its only argument and returns the actual range of the function. Unlike the return type function, the range function evaluates to the function’s range by assembling the results of applying the function to each element of its domain type. Determining the range of inc
is achieved by evaluating inc
on every value of its domain. Thus, the range of inc
is the natural numbers without zero. Formally:
ran(inc) == {x::dom(inc) | inc(x)} == posint;
where posint
is the name of the type containing all natural
values except 0. It is known that for any function, the range must be contained in the return type:
forall(f::function | ran(f) =< ret(f));
The domain and range functions present a greater challenge when dealing with functions of arity other than 1. It is possible to define a function with no arguments, often called a nullary function. During function evaluation, nullary functions are always reduced to expressions by removing the function former. Specifically:
<* ()::T is e *> == e
This is perfectly legal, as the purpose of the function former brackets is to define the scope of function parameters. With no parameters, there is no need to define scope. The domain of all nullary functions is the empty type containing no values. The range of a nullary function is the result of evaluating its associated expression. Thus:
dom(()::natural is <* 3+2 *>) == {} ran(()::natural is <* 3+2 *>) == {5}
Using these identities, one can define evaluation of a fully instantiated function as taking its range after instantiating and removing parameters. If all arguments to a function are known, then the range of that instantiated function is the same as evaluating the function.
Example 7.1. The Workhorse of Higher-Order Functions
Although seemingly quite innocent, the range function is the workhorse of the built-in higher-order functions. Virtually all the higher-order functions we will see involve calculation of range in their definition. Although it is not necessary to understand such definitions to use the functions, some understanding can provide valuable insight into the definition of other higher-order functions.
In reality, ran
behaves like an image function. Recall that an image function takes a collection of items such as an array or list and applies a function to each element. The difference between this mapping function and the Rosetta range function is that the initial set of values to be mapped is the domain of the specified function, while the function mapped to each element is the function definition. Given that we would like to apply the function f(x::D)::R
to the collection of items d
, we can use the image
function as follows:
image(f,d);
The ran
function is the same, except it takes each element of a function’s domain and applies the function to it. Using image
again and dom
to obtain the range of f
, this function can be implemented as:
image(f,dom(f))
This application of image
is semantically equivalent to:
ran(f)
Note that when actually defining Rosetta semantics, ran
is the primitive function and image
is defined from it.
The domain of functions with more than one parameter is defined as the type of the first parameter. This follows directly from the curried semantics, where every function can be reduced to a unary function. The range of functions with more than one parameter is defined as the type of the function resulting from curried evaluation with respect to the first parameter. Finally, the return type is the type syntactically specified as the return type of the function.
Given a find
function that determines if a value is in a sequence of values:
find(x::natural,y::sequence(natural))::boolean is if y/=null then if x=y(0) then true else find(x,(y sub [1,..#y-1])) end if else false end if;
domain, range, and return type are defined as:
dom(find) == natural
ran(find) == <*(y::sequence(natural))::boolean*>
ret(find) == boolean
The domain is the type of the first parameter and the return type is the type associated with the function. If this function were partially evaluated specifying only the first parameter, the resulting function type would be a mapping from an array of naturals to a boolean value. Thus, the range of the find function is the function type specified in the previous definition.
Example 7.2. Defining image
Using ran
The primary reason for including the ran
function in the Rosetta definition is its usefulness in defining other functions. One such function is image
, where a function is applied to elements of a set. In this definition, we will first construct a a function from image(f,S)
and evaluate ran
on the result. What we would like to apply ran
to is a function that accepts the elements of S
as its inputs and evaluates to the same value as f
. Such a function can be defined as:
<*(x::S)::ret(f) is f(x)*>
This function’s domain is the set S
used as one parameter to image
. Its value is f
evaluated on its input. To define image
we simply call ran
on the result:
image(f,S) == ran(<*(x::S)::ret(f) is f(x)*>)
The functions dom
, ran
, and ret
are defined to return elements of a function definition. Thus, it is natural to call these functions using functions as parameters. When thinking about image and mapping functions, Iminimum, maximum, and quantifier functions, the common practice is to start with a set of items and perform a specified operation on that set. Mapping functions apply a transformation to each element of a set, minimum and maximum return the appropriate value from a set, and quantifiers check to see if a property holds for all or some values from a set. Although each of these properties can be expressed using a higher-order function, it can become difficult to read and understand.
Rosetta introduces a standard notation to allow a more natural expression of higher-order properties involved in quantification associated with sets. The following notation is equivalent to finding the minimum value generated by inc
in the earlier example:
min(x::natural | x+1)
Using this notation, the domain and value expression of the function are separated in a manner consistent with traditional notations. The interpretation of this notation is to take all elements of natural
, apply the expression x+1
, and return the minimum resulting value. The mapping from this notation back to the higher-order function is a simple syntactic manipulation. If S is a set
and e is an expression defined over elements of S, then the following relationship is always true:
min (x::S | e) min(<*(x::S)::T is e*>)
Here the elements of the set are pulled out and used as a type in the function former. The notation:
min(x ::natural | x)
specifies the minimum value in the set of natural numbers.
What makes this notation powerful is that natural
can be replaced by any set and Rosetta forms the higher-order function. If the collection can be identified and the expression defined, the preceding notation can be used to accomplish the higher-order specification task. If additional single-parameter higher-order functions are defined, the same notation applies. Semantically, the notation adds nothing, but it significantly enhances the readability of specifications.
Table 7.2. Second-order quantifier functions
Operation | Format | Meaning |
---|---|---|
Minimum and Maximum |
| Minimum or maximum value from a function’s range |
Comprehension |
| Elements from a predicate’s domain that satisfy its definition |
Universal and Existential Quantifiers |
| True if a predicate is true for all or one of its domain values, respectively |
The primary functions used to define quantifiers are the minimum and maximum functions (Table 7.3). Each of these functions takes as its single argument an arbitrary function, finds the range of that function, and returns the minimum or maximum value in the range, respectively. In other words, the minimum function returns the smallest value a function can produce, while the maximum produces the largest.
Table 7.3. Applying built-in higher-order functions to the contents of sequences
Operation | Syntax | Meaning |
---|---|---|
Maximum and Minimum |
| Maximum or minimum value from the set resulting in F applied to a sequence |
Range |
| The set containing F applied to all values in a sequence |
Comprehension |
| Filtering contents of a sequence |
Universal and Existential quantification |
| Universal and existential quantification over contents of sequences |
The min
function takes a function and evaluates that function on every possible domain value and selects the minimum value. This can be viewed as taking the function’s range and returning the minimum of the range values. A simple function example is the identity function:
id(x::natural)::natural is x;
This function is equal to its single natural number argument. Applying the min
function returns its smallest possible value, or, in this case, the smallest possible natural number:
1. min (id) 2. == min (<*(x::natural)::natural is x*>) 3. == 0
The domain of the argument function is the type natural
. The range of the argument function is the expression applied to each element of the domain — specifically, the natural numbers. The min
function then returns the minimum value associated with natural or 0.
Similarly, the min
function can be applied to inc
where the expression associated with the function is x+1
. Specifically:
1. min (inc) 2. == min (<*(x::natural)::natural is x+1*>) 3. == 1
The max
function is defined similarly and operates in the same manner, except it returns the maximum value associated with a function’s domain, rather than the minimum value.
The concept of a quantifier from traditional logic allows specifiers to make statements about sets of items. The universal quantifier, called forall
, allows the specifier to claim that a property is true for every element of a collection. Similarly, the existential quantifier, called exists
, allows the specifier to claim that a property is true for at least one element of a collection. Although mysterious in many languages, Rosetta quantifiers are simply higher-order functions defined over predicates. In essence, we will take the range of a collection and use that range to determine if a quantifier holds. If the range of a predicate does not contain false
, the predicate is true for all elements of its range. Similarly, if the range of a predicate contains the true
value, there exists a value from the range for which the predicate holds.
Unfortunately, quantifiers and comprehension operators can be somewhat mysterious in their application and definition. In Rosetta, these operators are simply higher-order functions whose definitions do not differ substantially from simple minimum and maximum functions. Quantifier and comprehension functions and the other predefined higher order functions have the same form. The former can be instantiated with functions as their single argument:
forall(<*(x::S)::T is P(x)*>) exists(<*(x::S)::T is P(x)*>) sel(<*(x::S)::T is P(x)*>)
or using the equivalent, more readable mathematical notation:
forall(x::S | P(x)) exists(x::S | P(x)) sel(x::S | P(x))
where S is a set or type, T is a type, and P(x
) is a boolean expression defined over x
. The forall
and exists functions determine if P holds for all or some of the values in S, respectively. The sel
function selects elements of S for which P holds.
Consider the following application of forall
to determine if a particular set contains only values greater than zero:
forall(x::{1,2,3,5} | x>0)
Applying the expression x>0
to each element of the input collection, the resulting set becomes:
{true,true,true,true}
Thus, the expression is true
for each element of the input collection, meaning that the result of applying forall
is also true. Assuming the input set is {1,2,3,0}
demonstrates the opposite effect. Here, the result of applying the expression to each element of the collection is:
{true,true,true,false}
Because one value is false
, the forall
expression evaluates to false
.
Rosetta interprets the original definition by forming a function argument to forall
. In this case, the function has the following form:
forall(<*(x::{1,2,3,5})::boolean is x>0 *>)
Here, the domain of the argument function is the set {1,2,3,5}
and the result expression x>0
. To determine the range of the argument function, x>0
is applied to each element of domain collection giving the result presented previously. The exists
function works identically, but is true
when at least one of the evaluation results is true
, rather than all. For example:
exists(x::{1,2,3,-1} | x>0)
is true
because the result of applying the expression is {true, true, true, false
}and one element of the input collection is greater than 0. In contrast:
exists(x::{1,2,3,0} | x<0)
produces {false, false, false, false
}. None of the input collection is less than 0 and the exists
function evaluates to false.
Example 7.3. Using forall
and exists
as and
and or
One way to think of forall
and exists
is as general purpose and
and or
functions. Given some set of number
values S
and a predicate p
that determines if a value is greater than 5, the following definitions hold:
p(x::integer)::boolean is p>5; forall(x::image(p,S) | x) exists(x::image(p,S) | x)
Because p
is a boolean
function, the set that x
is selected from is boolean
. Thus, x
can simply be checked without transformation. The forall
function fails if it encounters a single false
value, while exists
succeeds if it encounters a true
value.
These applications are semantically equivalent to:
forall(x::S | p(x)) exists(x::S | p(x))
The sel
function provides a mechanism for selecting from a set values that satisfy a property, and returns them as a set. In traditional logic, the selection function is referred to as comprehension and is used as a primary method along with extension for constructing sets. Consider the following example, where sel
is used to filter out all elements of a set that are not greater than 0:
sel(x::{1,2,3,0} | x>0)
In this case, the result of evaluating the sel
function is the set {1,2,3}
, or the subset of the input set that is greater than zero. Like the previous higher-order functions, this form of the sel
function creates a function from the input arguments and operates on the range of that function. For this example, the equivalent form using a function argument is:
sel(<* (x::{1,2,3,0})::boolean is x>0 *>)
Example 7.4. Defining forall
and exists
It is interesting to note here that forall
and exists
behave identically to min
and max
for boolean
valued functions. The min
and max
functions applied in the same way would result in the same outcome with the following axiom defined:
true > false
With this axiom:
exists(x::1,2,3,-1 | x>0) == max (x::1,2,3,-1 | x>0) forall(x::1,2,3,-1 | x>0) == min (x::1,2,3,-1 | x>0)
In both cases, the result of applying x>0
to each element is {true, true, true, false
}. Interpreting these values under the assumption that true>false, true
is the maximum value in the result and false
is the minimum. These values correspond to the desired result of applying exists
and forall
, respectively. Thus, forall
and exists
simply provide more meaningful names for min
and max
. Furthermore, max
and min
are referred to as quantifiers in addition to the traditional forall
and exists
operators.
Example 7.5. Using the sel
Function
Just as ran
can be used to define image
, sel;
can be used to define filter
:
filter(p,S) = sel (x::S | p(x))
This application of sel
takes every element of S
, applies p
, and keeps the value from S
if p
is satisfied.
Example 7.6. Using sel
to Define Types
Possibly the most common use of the sel
function is to define new types. Chapter 8 discusses how any set can be used as a Rosetta type. All types defined in this and previous chapters are in fact sets of items. The sel
function is used extensively to define subtypes of existing types. For example, natural
is the subtype of integers
that includes zero and the positive values. The definition of natural
type is achieved using the following definition:
natural::subtype(integer) is sel (x::integer | x >= 0)
Note the use of the select function to filter integer
to get values greater than or equal to 0. Most number
types are defined in this manner.
There are two fundamental types of higher-order functions that are useful with respect to sequences. The first includes functions that simply treat the contents of a sequence as a set. Recall from Chapter 5 that a contents function is defined that extracts the contents of a sequence as a set. Given a sequence S
, its contents can be extracted as a set using the prefix operation ~S
. Thus, it is possible to apply any of the higher-order set functions to sequences. For example, given a sequence S
and a boolean expression P
, the set of objects from S
satisfying P
is defined as:
{sel(x::~S | P(x))}
The contents operator extracts the elements of S
as a set and the higher-order sel
function performs the comprehension. Table 7.3 shows how each of the defined higher-order functions for sets can be applied to sequences using the extraction function. Like the previous expression, this function evaluates to the subset of items from S
that satisfy P(x)
.
The second kind of higher-order function treats sequences as sequences generating new sequences from old sequences. Table 7.4 shows the definitions of these sequence functions. Rather than using the set-based higher-order functions, these operations are defined on sequences directly. The two built-in special operations on sequences are image
and filter
. The image
function takes a sequence and an arbitrary function and applies that function to each element in the sequence. To increment the contents of a sequence and maintain the result as a sequence, the map function is applied as:
image(inc,[1,2,3]) == [2,3,4];
Table 7.4. Special higher-order functions defined over sequences
Operation | Syntax | Meaning |
---|---|---|
Filter |
| Filter all elements from S that do not satisfy P |
Map |
| Apply F to all elements from S and return the resulting sequence |
Fold Left |
| Fold left |
Fold Right |
| Fold right |
Similarly, the filter
function takes a sequence and removes elements that do not satisfy a predicate. Assuming that the predicate lt3
exists that is true if its argument is less than three, filtering a sequence for values less than three is achieved by:
filter(lt3,[3,1,2,4]) == [1,2];
Anonymous functions and let
forms are particularly useful in conjunction with the image and filter operations. It is unlikely that the lt3
function just used will ever exist in any library. Using anonymous functions, the filtering operation can be implemented as:
filter(<*(x::natural)::boolean is x <3*>,[3,1,2,4]);
The use of filter
is identical in this example, except that the filtering predicate is defined locally and is discarded after the function is simplified and resolved. If a filtering or image function is used repeatedly, the let
form is useful for defining a local function:
letfilterFn::
<*(x::natural)::boolean
*> be <*(x::natural)::boolean is x<3*> filter(filterFn,[3,1,2,4]) end let;
Again the function is identical, but the local function filterFn
is defined in the let
form and is used in the filtering activity. Like the anonymous function defined previously, filterFn
is discarded following the closing of the let
form’s scope. It should be noted that using the let
form for local function definition in this way can be somewhat cumbersome. If filterFn
is used repeatedly within the let
form, or allowing the function to have a name increases readability, then the extra syntax is worthwhile.
Example 7.7. Using Set-Based Higher-Order Functions on Sequences
One application of higher-order functions like exists
and forall
is a form of comprehension over sequences. Using the contents extraction operation, one can extract values from a sequence and perform comprehension. Assume that we have sequence of integers
, S
, and we would like to determine if all sequence values are positive:
allPos(s: sequence(integer))::boolean is forall (y::(~S) | x>0)
allPos
extracts the contents of s
to a set and applies the test x>0
to each element returning true
if all elements are greater than 0.
Two operations on functions support comparison operations and composition of multiple functions into a single function. Function inclusion defines several relations that define when one function is, in effect, a sub-function of another. Specifically, we will define f=<g
, f<g
, and f=g
for any two functions. Function composition defines new functions by composing existing functions. The operation (f. g)
is defined to represent the function equivalent to the application of g
then f
to its argument. Specifically, we will define the composition operation so that (f. g)(x)==f(g(x))
.
Example 7.8. Using image
, filter
, and reduce
image
and filter
are exceptionally useful for manipulating sequences. Using image
, defining word functions from bit functions is a simple matter of applying functions to all bits in a sequence. Two functions that perform xor
and * (and) of one bit across an entire word are:
wordXor(b::bit; w::bitvector)::bitvector is
let helper ::<* (x::bit)::bit *> is
<* (x::bit)::bit is b xor x *>
wordAnd(b::bit; w::bitvector)::bitvector is image (__*__(b)) w
The two definitions represent two different approaches. The wordXor
function defines an internal helper function that applies xor
to the bit input to wordXor
and the single input bit x
. This function is then applied to each element of the input bitvector
. This approach is similar to that used in languages such as Scheme, where currying is not directly supported. The wordAnd
function uses currying to define a new function from the binary operation + and the input bit b
. This new unary function is applied to each element of the input vector adding the b
value with each element. filter
is equally useful for extracting values from a sequence or performing searches without resorting to primitive recursion. The following definition is for a function that determines if there are exactly two elements in a bitvector
with a value 1:
twoOnes(b::bitvector)::boolean is #(filter(<*(x::bit)::boolean is x=1*>,b))==2
Finally, we can define a parity checking function using reduce
over bitvectors:
evenParity(b::bitvector)::boolean is %(reduce xor 0 b)
The evenParity
function starts with 0 and applies xor
to the accumulated value and the current bit. As each 1 is visited, the accumulated value toggles. The %
operation is used to convert the resulting bit to a boolean
value.
The function type former <*(d::D)::R*>
defines the set of functions mapping D
to R
. This set is in all ways a Rosetta type and can be manipulated as a set. Thus, operations such as subset and proper subset are defined over functions. Subset applied to functions is referred to as a function inclusion operation. Function containment, f1=<f2
, holds when one function is fully contained in another function or function type. Assuming f1(x::d1)::r1
and f2(x::d2)::r2
:
f1
is contained in f2
if and only if the domain of f2
is a subset of the domain of f1
and for every element of f2
’s domain, f1(x)
is equal to f2(x)
. Exploring function inclusion’s several cases reveals where it applies.
The simplest case is when r1
and r2
are specified as sets, where the parameter x
is not involved in the definition. Examining the function inclusion law, the universal quantifier falls out and the following relationship results:
In this case, f1
is included in f2
when (i) dom(f2)
is contained in its domain and (ii) its range is contained in ran(f2)
.
A second case occurs when r1
is an expression and r2
is a set
. Instantiating the function inclusion law results in the following statement:
r2
is a constant value independent of x
. Therefore, the law requires that applying expression r1
to actual parameter x
results in an element of r2
. This is equivalent to the previous result and can be simplified to:
As an example, consider the increment function defined over natural numbers. It should hold that:
inc ::<*(x::natural)::natural*>
Instantiating the function inclusion law gives:
1. inc ::<*(x::natural)::natural*> 2. == natural =< dom(inc) and forall (x::natural | inc(x) in natural) 3. == natural =< natural and forall (x::natural | x+1 in natural) 4. == true and true
Thus, inc
is of type <*(x::natural)::natural*>
. It is interesting to note that this is exactly the relationship that must be checked for every definition of the form:
f(x::Td)::Td is e;
as it indicates that the actual function is of the same type as the specified signature.
The final case defines when one constant function is included in another constant function. In this case, both d1
and d2
are expressions and the most general expression of the function inclusion law must be applied.
First, consider determining if the increment function is included in itself. Clearly, this should be the case and the function inclusion law supports the assertion:
1. inc =< inc 2. == dom(inc) =< dom(inc) and forall (x::natural | inc(x) = inc(x)) 3. == natural =< natural and forall (x::natural | x+1 = x+1) 4. == true and true
This holds because for any Rosetta item, i=i
holds by definition. Consider the case of determining if increment is contained in identity over natural numbers. In this case, the law should not hold:
1. inc =< id 2. == <*(x::natural)::natural is x+1*> =< <*(x::natural)::natural is x* >; 3. == dom(id) =< dom(inc) and forall (x::natural | inc(x) = id(x)) 4. == natural =< natural and forall (x::natural | x+1 = x) 5. == true and false
false
is obtained from the second expression by the counter example provided by x=0
as 0+1 /= 0
.
When f1(d::d1)
is
e1, the following relationship holds:
f1 =< <*(x::d2)::r2*> ==
d2=<d1 and forall (n::d2 | f1(n) =< r2*>)
or
f1 ::<*(x::d2)::r2*> == d1=<d2 and ran(r1) =< r2
The function containment law gives the criteria by which one function may be said to be included within a function type. Each function type defines a set of functions consisting of all those functions included in it. This means that any function can be used as a type, or set, and all the containment laws for sets apply to them. This is particularly useful when using a function that returns a set rather than a single value. Consider the function <*(n::natural)::natural*>
. This function defines the set of all functions that take a natural number as an argument and return a natural number. Rosetta allows the user to ask if a given function is contained in that set and is a member of that type. For example, consider:
succ(n::natural)::natural is n+1;
We wish to determine:
1. succ(n::natural)::natural is n+1 :: succ(n::natural)::natural; 2. == (natural=<natural) and forall (n::natural | succ(n) in natural) 3. == true and forall (n::natural | (n+1) in natural) 4. == true and true 5. == true
Assuming that f(x::df)::rf
and g(x::dg)::rg
, the operations defined in Table 7.5 are defined over two functions. Functional equivalence checks to determine if every application of f
and g
to elements from the union of their domains results in the same value. Specifically, f(x) = g(x)
for every x
in either domain. Function inequality is defined as the negation of function equality.
The function composition operator takes two functions and composes them to form a third. The notation (f
• g)
represents the composition of functions f
and g
. The semantics of “g
then f
” is defined as:
(f. g)(x) f(g(x))
We define function composition because we can represent the composition without referencing its input parameter or parameter type. This allows writing functions in the point free style, a general style useful for writing abstract specifications.
A precondition on the application of function composition g
.f
is that every value to which g
can be reduced must be a legal input to f
. This relationship can be specified simply using the ran
and ret
functions:
ret(g) =< dom(f)
Example 7.9. Using Function Composition to Define a Double Increment Function
A simple application of function composition can be used to define a function that adds 2 to its input by composing the increment function with itself:
inc(x::integer)::integer is x+1; plus2 ::<*(x::integer)::integer*> is (inc. inc);
The composition is legal because the range of inc
is the same as its domain. Thus, ran(inc)=<dom(inc)
holds. A more interesting case deals with an increment function defined over naturals. Changing the definition slightly gives:
inc(x::natural)::natural is x+1; plus2 ::<*(x::natural)::posint*> is (inc. inc);
In this case, the range of inc
is posint
rather than natural
. However, posint=<natural
still holds and the composition is still valid.