Translation by Instantiation: Integrating Functional Features into an Imperative Language George Horatiu Botorog Herbert Kuchen Aachen University of Technology, Lehrstuhl fur Informatik II Ahornstr. 55, 52074 Aachen, Germany fbotorog,
[email protected]
Abstract In this paper, we present an imperative language enhanced with higher-order functions, function currying and a polymorphic type system. This enables high-level programming, as known from functional languages. On the other hand, this language can be eciently implemented, by translating the functional features into constructs of the imperative base language. An instantiation procedure generates for each (polymorphic) higher-order function one or more monomorphic rst-order functions. Since the transformation is done at compile-time, we have to make a restriction with respect to the functional arguments of recursively-de ned higher-order functions.
1 Introduction Eciency is a crucial issue in the implementation of functional languages, since the acceptance of these languages depends to a large extent on their performance. One way to raise this eciency is to enhance functional languages with imperative features. The integration of the two programming paradigms thus represents a means to obtain high-level languages which can be eciently implemented. In this paper, we shall present an alternative approach, namely an imperative language enhanced with some functional features. Although we have designed the language for a special purpose, i.e. as a host language for embedding algorithmic skeletons [2, 5], we address here the more general issue of eciently compiling these functional features. The language we present extends C by higherorder functions (HOFs), function currying, as well as a polymorphic type system. Since a direct implementation of HOFs and partial applications { for instance by closures { would lead to a considerable The work of this author is supported by the \Graduiertenkolleg Informatik und Technik" at the Aachen University of Technology.
loss of eciency, we shall eliminate these features at an early stage of the compilation by translating them into appropriate specialized rst-order functions. This is done by an instantiation procedure related to Wadler's higher-order macro-expansion [8]. Since polymorphism is also translated by instantiation, a single transformation is employed. The paper is organized as follows. Section 2 presents the host language with an emphasis on the functional enhancements. Section 3 rst gives a series of examples on instantiation and derives the limitations of the technique from them. After that, a formal description of the transformation is given. Section 4 illustrates the instantiation with an example and Section 5 presents some related work.
2 The Language The language we shall de ne builds on top of a Csubset by enhancing it with a series of functional constructs. The goal is to make speci cations of functions similar to those in functional languages possible. As an example, consider the function map, which applies a given argument function to a given list of items, yielding a list of results. In Haskell, this function could be de ned as follows: map ::
(a -> b) -> [a] -> [b]
map f [] = [] map f x:xs = f x :
map f xs
The polymorphic type of the function is achieved by using type variables, here a and b. Although polymorphism can be simulated in C by casting and void pointers, this eludes the type checking performed by the compiler, leading to error prone programs. We therefore chose to explicitly support polymorphism, and enable a polymorphic type checking which leads to safer programs. We enhanced the language with type variables, which have the form $t and denote arbitrary types. Type variables may be used in any type declaration
brackets, i.e. (op) (arg1 ,arg2 ) arg1 op arg2 . Thus, operators can be passed as functional arguments to HOFs, and can be partially applied. This is illustrated by the next example, in which all elements of a list are multiplied by 2:
or speci cation. A type is considered to be polymorphic i it contains at least one type variable. In order to simplify the instantiation of polymorphic structures and unions, the type variables appearing in the components have to be passed as parameters to the structure de nition, by enclosing them between sharp brackets (). For instance, a polymorphic list can be declared as follows1:
map ((*)(2), lst)
3 Translation by Instantiation
typedef struct _list { $t elem ; struct _list *next ; } *list ;
The translation of functional features by instantiation builds for each higher-order and/or polymorphic function a number of rst-order monomorphic specialized functions (`instances'), as needed in the calling program. The technique is related to the higher-order macro-expansion [8], but has the advantage of providing a unitary framework for translating higher-order functions, partial applications and polymorphism. The target language is the initial imperative language. We shall rst present some sample instantiations and derive from them the limitations of the concept. We shall then impose some constraints on the functional arguments to ensure the termination of the transformation. Finally, after some formal preliminaries, the actual instantiation procedure is presented.
From this generic list type, a monomorphic one can be created by supplying some type for the parameter. For instance, a list of integers can be declared as list . The next feature we consider are higher-order functions. Although this feature could also be simulated in C by passing pointers to functions, this would not allow the use of functional arguments created by partial applications. Hence, we allow functions to have functional arguments as well as a functional return type. The above function map can then be de ned as follows, provided the standard list functions nil, cons and empty are available:
3.1 Some Examples and Limitations
list map ($t2 f ($t1), list l){ if (empty (l)) return (nil ()) ; else return (cons (f (l->elem), map (f, l->next))) ; }
We shall rst concentrate on higher-order functions, whereas the issues of partial applications and polymorphism will be addressed later on. Suppose we have the following (monomorphic) de nition of map:
Closely related to HOFs are partial applications, i.e. applications of functions to fewer arguments than their arity2 . Functions are thus curried in our language. Partial applications are useful in creating new functions at runtime. As an example, suppose we want to compare all elements of a list lst with some given threshold value, using the binary function `less than' (lt). This can be done by calling the function map, with a partial application of lt as its functional argument:
list map (int f (int), list l){ ... cons (f (l->elem), map (f, l->next)) ... }
Then the call map(log,lst) can be instantiated together with the de nition, yielding map_1(lst) and list map_1 (list l) { ... cons (log (l->elem), map_1 (l->next)) ...
map (lt (thresh), lst)
}
The instantiation was performed by removing the functional parameter f from the header of the de nition of map and replacing it by the actual argument log in the body. Note that instantiating the recursive call of map poses no problems, since the argument f is just the formal parameter of the function. Thus, the instantiation can be handled using a table of already made instantiations and comparing each new HOF to be instantiated with the instances in the table3 . In this example, the instance map_1 is
In addition to constructs presented above, we shall include some others of lesser importance, who aim at increasing the programming convenience and the eciency of the implementation, like for instance the Haskell-like conversion of operators to functions. This is done by enclosing the operator between 1 This is actually the imperative counterpart of the functional list type [a], de ned here as a product type, rather than a sum-of-products type, since the internal NULL pointer is used instead of the [] constructor. 2 The arity ar(f ) of a function f is de ned as the number of formal parameters in the de nition of f .
3 This also avoids the duplication of equivalent instances in the general case.
2
Let t0 f (t1 a1 , ..., tn an ) body be a HOF de nition. Then for each application of the form f (expr1 ; :::; exprk ) in body and 8i 2 f1; :::; kg such that functional (type (expri )) and expri = gi (bi;1 ; :::; bi;r ) and 8l 2 f1; :::; ri g such that functional (type (bi;l )) and 8j 2 f1; :::; ng such that functional (tj ), we have: bi;l does not have the form aj (z1 , ..., zs ), s 0. i
Figure 1: Termination condition for directly recursive HOFs instantiation, not only to the recursive calls in the body of the de nition. For that, it is necessary to keep track of all nested instantiations. Only thus can potential non-termination in cases such as the following be detected.
created upon encountering the call map (log,lst) and used to instantiate the recursive call of map in the body of its de nition. There are nevertheless cases in which instantiation cannot be performed, since it would end up in a nonterminating computation. Let us consider rst the functional \", which computes the function composition, i.e. (f g)(x) = f (g(x)). This functional can be de ned as:
f (g, x) { ... h (g, x) { ...
On the other hand, our condition allows the instantiation of HOFs like:
$c compose ($c f ($b), $b g ($a), $a x) { return (f (g (x))) ; }
f1 (g, h, x) { ... f1 (h, g, x) ... f2 (g, x) { ... f2 (id, x) ... }
Using this functional, we can de ne the following HOF4 : f (g, x) { ...
}
The condition (C) is not `too restrictive', since most HOFs used in real programs pass their functional parameters unchanged to their recursive calls, like the map function does. Apart from functional arguments, HOFs may also have a functional return type. The translation of this feature is done by -conversion, i.e. by supplying additional arguments which `account for' the parameters of the function returned. These parameters are inserted both in the header and in each `return' expression in the body of the function. We shall illustrate this with an example. Suppose we have the following function which selects one of its functional arguments, assumed to be unary functions:
f (compose (g, g), x) ...}
The transformation of this function generates a non-terminating sequence of instances, corresponding to the functional arguments g, g2 , g4 etc. Such situations can be ruled out by imposing certain restrictions. In [8], this restriction is that HOFs may not have higher-order functions as arguments in recursive calls. In [5] the condition is posed that each functional argument of a HOF is passed unchanged in recursive calls of the HOF. Since the second condition is more restrictive than the rst, we shall use the rst one, though further broadened to cover cases like: f (g, x) { ...
h (compose (g, g), x) ...} f (g, x) ... }
select (f, g) { if (f (0)) return (f) ; else return (g) ; }
f (compose (h, l), x) ...}
This de nition can be translated to one having a non-functional return type by supplying an additional argument:
where h and l do not appear as parameters in any enclosing call to f. We shall formulate the condition on the instantiation of HOFs as follows: (C) If a HOF is de ned recursively, then in all its recursive calls, no argument with functional type may contain an application to a functional parameter of the considered HOF. In case of direct recursion, this condition can be formally stated as shown in Figure 1. However, due to mutual recursion, the condition must be observed with respect to all nested HOF calls of the current
select_1 (f, g, x) { if (f (0)) return (f (x)) ; else return (g (x)) ; }
Applications of the old function are replaced by partial applications of the newly obtained one. For example, the call map (select (sin, log), lst)
will be converted to
4 In this and some of the following examples, we shall use a simpli ed syntax (omitting, for instance, types where they are irrelevant) and highlight only the important aspects.
map (select_1 (sin, log), lst)
3
3.2 Formal Preliminaries
whereas the partial application of select_1 will be eliminated during the instantiation, as shown below. We shall now turn to partial applications. Consider again the call
We can now de ne some sets for our language, which we shall use in the translation, based on the corresponding sets for the base language. The set of types Type is de ned over the basic C types and the type variables, as the closure with respect to the application of the C type constructors array, pointer, struct, union and function:
map (lt (thresh), lst)
In order to instantiate it, together with the de nition of map, the partial application of lt is replaced by the list of its arguments (here thresh) and inserted into the body of map, yielding the new call map_2 (thresh,lst) and the de nition: map_2 (x, l) {...
Type := f(btype j tvar) j btype 2 CBaseType, tvar 2 TVar, = closure under C type constructorsg The set of function symbols Fun is de ned as the union of the C function symbols and the operators converted to functions:
cons (lt (x, l->elem), map_2 (x, l->next)) ...}
Note that we could not have applied here the same technique as in the case of `plain' functional arguments, i.e. eliminate the argument and insert it into the body of the HOF, since the result would have been map_3 (lst) and: map_3 (l) {...
Fun := CFun [ f(op) j op 2 COpg The set of expressions Expr is de ned as the union of the set of C expressions, the set of function symbols and the set of applications of function symbols to a number of expressions not necessarily equal to their arity:
cons (lt (thresh, l->elem), map_3 (x, l->next)) ...}
Expr := CExpr [ Fun [ ff (expr1 ; :::; exprn ) j f 2 Fun, expri 2 Expr, i = 1; :::; ng
If the variable thresh is de ned in another environment, then its insertion into the body of map_3 brings it out of scope5 . Hence, non-functional arguments have to be passed as arguments to HOF instances, unlike functional arguments, which are de ned on the top level. The translation of partial applications works similarly to -lifting [4], with the dierence that not all parameters are added to the `lifted' function, but only the ones with local scope. Should the functional arguments have functional arguments of their own, then the latter have to be instantiated rst. The instantiation procedure is thus performed innermost-to-outermost. A polymorphic function is translated to one or more appropriate monomorphic instances. Recall the (polymorphic) de nition of map from Section 2. Upon encountering a call which, say, computes the codes for a given list of characters (i.e. converting a list to a list ), this function is instantiated to list map_4 (int f (char), list l) { ...
In order to avoid code duplication in case of equivalent HOF calls and as an aid to the instantiation of nested or recursive HOFs, a table of instances is de ned. This table contains for each HOF a list of records of all instances created for that HOF. Each record contains the name of the function symbol, the arity of the HOF call and the type and name of each argument of the call. If the type of an argument is functional, then the name eld is set to the name of the function, otherwise it is empty ("). If the functional argument is a partial application, then the name of the outermost function applied is used. The types in the record are needed in the instantiation of polymorphic HOFs, as shown later. InstRec := f(inst, k, type1, name1, ..., typek , namek ) j inst 2 Fun, k 2 IN, typei 2 Type [ f"g, namei 2 Fun [ f"g, i = 1; :::; kg Insts := Fun ! P (InstRec)
}
This instance can now be further transformed with respect to its functional arguments. In the procedure presented in the following, the instantiation of higher-order functions, partial applications and polymorphism is done simultaneously.
Using the table of instances, the translation procedure can check if an instance equivalent to the current one has been created before. Two HOF instances are considered to be equivalent i they have the same arity, i the types of their arguments are pairwise equal and i the functional arguments consist of calls or partial applications of the same functions, respectively. Since the instantiation can be
5 Nevertheless, a possible optimization would be to use this technique for the constant arguments of a partial application, since these are not bound to a certain scope.
4
Let f (expr1 ; :::; exprk ) be a HOF call, f 2 Fun, exprj 2 Expr and finst1; :::; insts g := insts (f ), insts 2 Insts. If 9 p 2 f1; :::; sg such that instp = (inamep , kp , typep;1, namep;1, ..., typep;k , namep;k ) and kp = k and 8i 2 f1; :::; kg typep;i = type (expri ) and functional (typep;i ) ) expri = namep;i (bi;1; :::; bi;r ) then instp is equivalent to the instance that would be created for the call. p
p
i
Figure 2: Check for equivalent instances performed more eciently if an equivalent instance already exists, the check is done before transforming the current HOF call, by matching the call against the instance records previously created for that HOF (see Figure 2). In addition to the instance table, a list of current instantiations has to be kept. This list helps to detect possible cases of non-termination, in direct or mutually recursive higher-order functions and consists of the records of the nested instantiations currently active: CrtInsts := Fun ! P (InstRec). An instantiation of a HOF call terminates, if condition (C) holds for all current instances of that HOF. Pragmatically, (C) is checked by comparing the newly created instance record against all active ones. We further use a table that helps us to retrieve a function by its name: FunDefs := Fun ! FunDef, where FunDef is the set of function de nitions. The last function we need for the instantiation procedure is the substitution. We use substitutions in the classical sense, but allow not only variables, but also entire (sub)expressions { viewed as patterns { to be replaced, provided they do not overlap with their replacing expressions. Substitutions are written in post x notation. Thus, con[expr=expr0 ] represents the construct con (which might be an expression, a statement etc.) in which all occurrences of expr have been replaced by expr0 . The set of all substitutions is denoted by Subst.
to the creation of an instance, as well as the local list crtinsts of currently active (i.e. nested) instantiations. If the function f is neither higher-order nor polymorphic, then I applies itself to all applicative expressions in the body of f , leaving the initial expression expr unchanged. Otherwise, I checks if an equivalent instance of f has already been created. For the case that no such instance exists, a new instance record instrec is created with the arity set to k and all name and type elds set to ". After that, each argument of the HOF call is processed by the step function S described below. In each run, S transforms the j th argument of the HOF call, i.e. exprj . However, since each of the foregoing arguments expr1 , ..., exprj?1 was replaced by 0, 1 or more new arguments (in case of partial applications), exprj was shifted to a new position in the argument list pointed to by the extra index i. In case no equivalent instance already exists, S also performs a stepwise update of the de nition of the new instance (instdef ) and of the instance record (instrec). Further, if instdef is polymorphic, S successively constructs the substitution that uni es the type of instdef with that of the call of f . Note that, since expressions are transformed starting from the main function, which must be monomorphic, and since the polymorphic types in a function de nition are instantiated before I is called (see Figure 3), the HOF application f (expr1 ; :::; exprk ) is always monomorphic. After the function S was called for all arguments, if no equivalent instance already exists, then the termination condition (C) is checked. This is done by comparing the newly created instance record instrec against the records of the currently `active' instances crtinsts(f ). If the condition is ful lled, then the polymorphic types in instdef are instantiated to monomorphic ones using the substitution subst. After that, the creation of the instance record and of the instance de nition has to be completed. The record is provided with a fresh function name for the new instance, which is also entered into the de nition header. In addition, the body of the de nition must be instantiated, too. For that, the now completed record is added to the table of instances insts, as well as to the list of nested instantiations crtinsts. The body of the de nition is processed by
3.3 The Instantiation Procedure
We shall now describe the instantiation of the functional features. This is done by the function I shown in Figure 3, which is executed for all applicative expressions, i.e. for all expressions that have the form f (expr1 ; :::; exprk ), k 1. These applications are run through starting from the main function and recursively continuing with the body of each function called. Since HOF calls may be nested, I is applied innermost-to-outermost, i.e. rst to expr1 , ..., exprk , and then to the overall call. Apart from the expression to be instantiated, I has access to the global table fundefs containing all function de nitions, including those of the created instances, which it may modify by adding its own instance(s). Further, it may access the global table insts with information on each HOF call that led 5
I : Expr ! Expr I (expr) := let expr have the form f (expr1 ; :::; exprk ), where k 1 instdef := fundefs (f ) ishof := 9i 2 f1; :::; kg such that functional (type (expri )) if (not ishof and not polymorphic (type (instdef )))
then call I for all applicative expressions in the body of instdef exit with result expr search in insts (f ) for an equivalent instance for expr instrec := (", k, ", ", ..., ", ") / 2k `"' / iexpr := expr i := 1 subst := [ ] for j := 1 to k repeat (iexpr, i, crtsubst) := S (iexpr, instdef , instrec, i, j ) subst := append (subst, crtsubst) if an equivalent instance was found then let iname be the name of this instance else check condition (C) for instrec against all instances of crtinsts (f ) if (C) is not ful lled then exit with error instdef := instdef [subst] let iname be a new name update instrec and instdef with iname add instrec to insts (f ) and to crtinsts (f ) call I for all applicative expressions in the body of instdef delete instrec from crtinsts (f ) add instantiated instdef to fundefs result iexpr[f=iname] Figure 3: The instantiation procedure
performing the transformation I on all applicative expressions within. After processing the body, the current instance record is removed from crtinsts and the resulting instance de nition instdef is added to the set of function de nitions. Finally, the name of the HOF is replaced by the name of the instance in the transformed expression. The step function S (Figure 4) instantiates the argument at the j th position in the initial HOF call, which was shifted by the transformation of the rst j ? 1 arguments to the ith position, becoming expri in the current state of instantiation of the call. The index i also points to the current parameter in the instance de nition, since the parameters in the de nition are replaced simultaneously with those in the call. On the other hand, j is used as an index on the instance record, because the latter re ects the structure of the initial HOF call. Apart from these parameters, S modi es the current instance de nition instdef and instance record instrec. S rstly sets the type of the j th item in the instance record to the type of expri . Further, it checks if the type of the ith parameter in the de nition is
polymorphic and if so, uni es it with the type of expri yielding the substitution subst. After that, if the type of expri is not functional, then the instantiation of this argument is nished. Otherwise, expri , which is a function application to 0 or more arguments (bi;1 ; :::; bi;r ), is replaced by these arguments. Note the unitary processing of both `plain' functional arguments and those resulting from partial applications. If no equivalent instance already exists, then the instance record and the instance de nition must also be updated. The rst update is done by setting the name of the j th argument to gi . The second update is performed both on the header and on the body of the function de nition. In the header, the ith parameter and its type are replaced by a list of new parameters (yi;1 ; :::; yi;r ) having the types of the new arguments of the call. In the body, each call of the eliminated formal parameter argi is replaced by a call of the function gi (de ned elsewhere in the program), with the new parameters yi;1 ; :::; yi;r as additional arguments. Concluding this section, we shall address the issues of termination and correctness of the instantiation. i
i
i
6
S : Expr FunDef InstRec IN IN ! Expr IN Subst S (expr, instdef , instrec, i, j ) :=
let expr have the form f (expr1 ; :::; exprl ) and instdef have the form type0 f (type1 arg1 , ..., typen argn ) body and instrec have the form (", k, t1 , a1 , ..., tj?1 , aj?1 , ", ..., ") t := type (expri ) instrec := instrec[tj =t] if polymorphic (typei ) then subst := [typei =t] else subst := [ ] if not functional (t) then result (expr, i + 1, subst) else let expri have the form gi (bi;1 ; :::; bi;r ) newexpr := f (expr1 , ..., expri?1 , bi;1 , ..., bi;r , expri+1 , ..., exprn ) if no equivalent instance already exists then instrec := instrec[aj =gi ] let yi;1 , ..., yi;r be new variables instdef := type0 f (type1 arg1 , ..., typei?1 argi?1 , type(bi;1) yi;1 , ..., type(bi;r ) yi;r , typei+1 argi+1 , ..., typen argn ) body[argi (z1 ; :::; zs )/gi(yi;1 ; :::; yi;r ; z1 ; :::; zs )] result (newexpr, i + ri , subst) i
i
i
i
i
i
Figure 4: The auxiliary step function
Claim 1 (termination) The instantiation procedure, as de ned by the function I , terminates.
Motivation: Suppose that the resulting program contains a HOF. Since the transformation is performed on all expressions in the reachable part of the program, this HOF could only appear in the noninstantiated body of a generated instance. Let this instance be instdef1. Since its body is not instantiated, the `then'-branch has been followed in the big `if' in I during its processing, i.e. an equivalent instance already existed. We shall call this instance instdef2. But if instdef2 existed, then instdef1 would not have been created in the rst place. This contradiction implies that instdef1 cannot contain HOFs. Analogous deductions can be made for partial applications and polymorphism.
Motivation: The rst observation is that the function I is idempotent as of its second application: I m (expr) = I (expr), 8 m 1, 8 expr 2 Expr, i.e. only the rst application of I to a HOF call can instantiate it, all subsequent applications leave it unchanged and exit without starting new instantiations. Since the number of applicative expressions in a program is nite, the only possibility to generate a non-terminating transformation, is to constantly start instantiations of expressions in bodies of function de nitions, for which no previously created equivalent instance can be found. Since the equivalence test is mainly a test on the functional arguments (polymorphism is translated in one step), this can only be achieved by (mutually) recursive HOF de nitions, in which at least one functional argument \constantly changes". An endless change of a functional argument can only be the result of a function being applied to it, but could not be generated by other means like, say, permutation with other functional arguments. However, this case is ruled out by the condition (C), so that termination is ensured.
4 An Example We shall now illustrate the way the instantiation of HOFs and partial applications works with an example. We do not address the translation of polymorphism, since this is done exactly as in the last example in Section 3.1. Since most real examples are easy to instantiate { consider for instance the function map { we have arti cially constructed the following (recursive) de nitions of the HOFs inner and outer:
Claim 2 (correctness) Given a program in which the functions can be higher-order, partially applied and polymorphic, the result of the transformation I
extern int p (int, int, int) ; extern int q (int, int) ; extern int t (int, int) ;
is a rst-order monomorphic program, in which all function applications are total.
7
Again, the recursive call to outer has to be processed. This time, though, the instantiation procedure nds outer_1 to be equivalent to the instance that would be created here, so it only transforms the expression to the call outer_1 (v7, v8, v6, x-1). This concludes the instantiation of our initial call.
int inner (int f3 (int), int y) { return (f3 (log (y))) ; } int outer(int f1(int), int f2(int), int x){ if (x == 1) return (f1 (x)) ; else return (outer (f2, f1, x - 1)) ; }
5 Related Work
Consider further the nested higher-order function call outer (inner (p (1,2)), q (3), t (4,5)). Upon comparing the calls of p, q and t with their prototypes above, we see that p and q are partially applied, while t is totally applied. We shall now describe the main steps of the instantiation. We start with the innermost HOF call, i.e. the call to inner. This HOF has as functional argument the partial application of p, which is replaced by its arguments a and b. At the same time, the de nition of inner has to be instantiated. This is done by replacing f3 in the formal parameter list by two new arguments (v1 and v2) and its call in the body by p (v1,v2). The latter replacement yields p (v1,v2)(log(y)), which is then uncurried to p (v1,v2,log(y)). After that, the body of the instance has to be processed. As it contains no HOF calls, it remains unchanged. The resulting call is thus inner (a,b), and the corresponding de nition is:
Ecient implementation of functional languages via translation to an imperative language (mostly C) has been addressed in many papers, like for instance [1] and [3]. The dierence to our approach is that these translations are not `structural', i.e. there is no correspondence between the control structures in the source program and those in the intermediate code. An integration of Scheme and C is described in [6], however this is done on module level. The approach closest to ours is [7]. Here, a functional language is translated into \handwritten-like" C-code, using inlining where possible, but otherwise returning to closure-techniques. Our method, which does not use closures, should thus lead to more ecient programs.
References [1] J. Bartlett: SCHEME{>C: a Portable Schemeto-C Compiler, WRL Research Report 89/1, DEC Western Research Lab, Palo Alto, 1989. [2] G. H. Botorog, H. Kuchen: Algorithmic Skeletons for Adaptive Multigrid Methods, in Proceedings of IRREGULAR '95, LNCS 980, Springer, 1995. [3] S. J. Croft: Compiling a Lazy Functional Language to C, in Proceedings of CC '92, Technical Report 92{103, University of Paderborn, 1992. [4] T. Johnsson: Lambda Lifting: Transforming Programs to Recursive Equations, in Proceedings of FPCA '85, LNCS 201, Springer, 1985. [5] H. Kuchen: Datenparallele Programmierung von MIMD-Rechnern mit verteiltem Speicher, Thesis (in German), RWTH Aachen, 1995. [6] J. Rose, H. Muller: Integrating the Scheme and C Languages, in 1992 Conference on Lisp and Functional Programming, ACM Press, 1992. [7] M. Serrano, P. Weis: Bigloo: a Portable and Optimizing Compiler for Strict Functional Languages, in Proceedings of SAS '95, LNCS 983, Springer, 1995. [8] P. Wadler: Deforestation: Transforming Programs to Eliminate Trees, in Theoretical Computer Science, No. 73, North-Holland, 1990.
int inner_1 (int v1, int v2, int y) { return (p (v1, v2, log (y))) ; }
We now instantiate the outer HOF call, which has the form outer (inner_1(a,b), q (c), t(d,e)). The rst two arguments are partial applications and are dealt with like in the previous case. The last argument is non-functional (total application) and therefore remains unchanged. After two steps we obtain outer_1 (1, 2, 3, t (4,5)) and the de nition: int outer_1(int v3, int v4, int v5, int x){ if (x == 1) return (inner_1 (v3, v4, x)) ; else return (outer (q (v5), inner_1 (v3, v4), x-1)) ; }
We now have to process the body of this de nition. This amounts to the instantiation of the call of outer in the else-branch. This is done analogously to our initial expression, yielding the new call outer_2 (v5, v3, v4, x-1) and the de nition: int outer_2 (v6, v7, v8, x) { if (x == 1) return (q (z1, x)) ; else return (outer (inner_1 (v7, v8), q (v6), x-1)) ; }
8