Information Processing Letters 24 (1987) 255-258 North-Holland
2 March 1987
STRICT C O M B I N A T O R S
Silvio Romero de Lemos M E I R A Department of Computer Science, Federal Unioersity of Pernambuco, 50000 Recife, PE, Brazil Communicated by W.L. Van der Poel Received 23 April 1986
The idea of strict combinators is presented, and its use in the implementation of purely functional programming languages is discussed. Examples are given and the effect of the proposed implementation of such combinators on performance is also discussed.
Keywords: Turner combinators, strictness, strict combinators, BC-chains, MIRANDA
1. Basics
Ih [1] we have introduced the idea of strictness analysis of combinator code, as opposed and equivalent to strictness analysis at source language level. One application of strictness analysis to higher-order, nonstrict functional languages is to discover the parameters in which a function definition is strict, and then use this information to transform the usual method of parameter passing in such languages, call-by-need, into the more efficient, but less powerful, call-by-value. The idea is further developed in [2], and here we intend to present a practical way of implementing the mixed parameter passing mechanism. Another use of strictness analysis on implementations of functional programming language is to distribute work in parallel implementations, and the application of this idea to the combinators method is discussed in [5]. By just using strictness analysis to transform call-by-need into call-by-value, we can avoid most of the unnecessary consumption of space by strict functions, which, however, do not make use of some of their parameters until the final result is to be delivered. As a result of that characteristic, nonstrict ('lazy', operationally) definitions of such functions will have unevaluated applications--
sometimes called closures--left hanging in the node memory and occupying space. In [2] we showed several cases in which this occurs, and also showed that the use of strictness analysis to transform call-by-need into call-by-value can reduce the space complexity of a large family of definitions by an order of magnitude. In this paper, we discuss how this proposal can be made practical, and what effects it may have on combinator machine performance.
2. The R combinator
We introduce a combinator R (for Reduce), which will be the basis of our implementation, and give its semantics. First, let us note that, if x is a combinator term, and x r its normal form if it exists and ./_ otherwise, then if f is a strict function, we have fx=fx
r.
This follows by the very definition of strictness. Then, let R be the combinator whose operational semantics is defined by Rfx=fx
r,
i.e., the only effect of R is to force the evaluation
0020-0190/87/$3.50 © 1987, Elsevier Science Publishers B.V. (North-Holland)
255
Volume 24, Number 4
INFORMATION PROCESSING LETTERS
of its second parameter. Obviously, if f x = f x ~, then we have the semantic identity R = I, where I is the identity c o m b i n a t o r I x = x. Hence, the introduction of R as a prefix of f x does not alter the meaning of the latter. The function of strictness analysis, in this case, is to identify, for an n - p a r a m e t e r function definition, f Pl P2.-- Pn . . . .
,
the Pi in which f is strict. A later pass would be the introduction of the R combinators n e e d e d to c h a n g e the m e t h o d of passing p a r a m e t e r s - - i f any - - b y value instead of need. In general, in a function f of n parameters, strict in all of them, n R combinators would have to be introduced in its new, strictified definition, so that at most O(n) R combinators would be a d d e d to the initial c o m b i n a t o r term. With the R c o m b i n a t o r defined above, though, O(n 2) combinators will be introduced in the code, because of the use of B combinators n e e d e d to i m p l e m e n t the R reductions properly. To see how a n d w h y that happens, suppose we have a function f strict in all of its parameters. F o r example, assume that f has three parameters only, and think of f as being the definition of "t"'. W h a t we need to have is fxyz=fxryrzr.
R(BR(B(BR)
f))xyz
=BR(B(BR)
f)x ryz
=R((B(B.R)
f) x r) y z
=BR(fxr)
fx r y~z yr z
= R ((f x~) y r ) z =((fx~)y~)zr=fxryrz
r.
N a m e l y , for the ith p a r a m e t e r of f, we need a B R 'prefix' with i - 1 B's a n d one R, as shown above for the case where the n u m b e r of parameters is 3. If this is indeed the case, then O(n 2) combinators are needed to i m p l e m e n t such mechanism. If f is not strict in its ith parameter, then the ith R, f r o m left to right, is substituted b y an I combinator. Corresponding to the increase in the size of 256
the object plexity of creased by f is always
code b y O(n2), the overall time c o m the evaluation of the function is ina factor of O(n 2), because the prefix of reduced.
3. The Ri,
Rpi, and R e (i:x)
combinators
N o w we i n t r o d u c e a possibly infinite family of combinators Ri, which strictify exactly the (i + 1)st p a r a m e t e r to each R i. Thus we have R 0 x = x r, R 1 fx=fx
r,
R 2 fxy=fxy °
•
•
~
•
.
r,
•
N o t e that we have R 1 = R and also that each Ri, for i >/1, is equivalent to a B R term that contains (i - 1) B's. The r e a d e r can easily check this from the terms in the previous section• Now, for the same f in Section 2, we can construct its strict version as R 1 ( R E ( R 3 f)) x y z , which can be reduced as R 2 ( R 3 f)
X r
y z
= R 3 f x r yr z = f x r yr Zr
Then, using R and B as prefixes of f, we have
=B(BR)
2 March 1987
.
In practice, we do not need an infinite n u m b e r of R i combinators, because the functions definable in real functional languages have only a finite n u m b e r of parameters. Then, by restricting the m a x i m u m n u m b e r of formal parameters in a function definition to some reasonable number, we could do with a finite, small n u m b e r of R i. Also, we can use another method, which depends on a single, parameterized Rp combinator, whose first p a r a m e t e r is i, the index of the p a r a m eter, after the second, whose n o r m a l form is to be computed. T h a t idea can be turned into the following equations defining the operational semantics of R p: Rplfx=fx
r,
Rp n f x = Rp ( n -
1) (f x ) .
This a p p o a c h is loosely based on the pair of
INFORMATION PROCESSING LETTERS
Volume 24, Number 4
combinators TRY and MATCH, used by Turner to implement pattern matching in the language SASL
161.
the R, combinators with proper indices, and combining them with I’s in the right places in the term, which will cope with nonstrict parameters, we eliminate the need for B’s, which was the main cause of our previous 0(n2) space and time for strictifying a definition. The only remaining problem is that we still need O(n) R, combinators to strictify n parameters. There could be a number of reasons for which we would prefer having a single R combinator per function definition, including saving space. One possible solution to this problem is to define an R, combinator as By using
R,[]f=f, R,
(0:~)
f x=R,
p (f x),
R,
(1: p) f x = R, p (f x’) ,
where the list-i.e., the first argument of R,is the “strictness attribute’ for a given definition (f . . _), that is to say, for each parameter pi to f, f is strict in pi if ei is 1, where pi is the i th element of /. The list is enough to cope with cases where we deal with scalar parameters, and its counterpart for dealing with nonscalar parameters is a trivial development of the case above. Namely, we just need to make the list e a list of lists, each of which is the strictness attribute for a given parameter. If the parameter is scalar, the list is unary, otherwise it has as many values as may be needed to specify the strictness information for the definition, regarding the parameter in question. Let us give one example for the scalar case, considering the definition of R, given above. Assume we have the trivial function definition saddabc=a+c, which is strict on the first this definition is originally tor term 7, we can use R, information and have the R,
[l,
and third parameters. If compiled to a combinato codify the strictness final term
0, 117.
In the case of this very trivial function, we have nothing to gain by adding strictness information
2 March 1987
to the function definition. Due to the fact that we chose to add strictness information via a combinator, and not a quirk in the implementation, we could (that is to say, the compiler could) decide not to add the strictness information in this case. That would make no difference at all in the way our program would run. Now, if we had the definition add a 0 = a , add a b = add (a + 1) (b - 1)) which is strict in both parameters, adding strictness information does make sense. Note that the first parameter is needed only at the end of the evaluation, and this is enough to generate an O(b) closure. A call-by-value implementation of this function would evaluate it in constant space, and that is exactly the effect we get by transforming the (original) combinator term, 4 say, into R,
[l, 11 9 -
An interesting implementation detail is that the list of strictness attributes is, in fact, a list of boolean values, which can be implemented as a bit list. In architectures designed with the implementation of functional languages in mind, this detail can be taken care of by special tags in the data objects, as is the case in the reduction machine proposed in [3]. In that architecture, the pair consisting of an R combinator and a list containing the strictness attributes for definitions with less than 64 parameters can be encoded in two nodes of information. It is very realistic to assume that most definitions will have less than 64 parameters and, for this reason, the practical complexity of introducing the strictifying combinator R, described in this paper is a constant. The R, combinator defined above will also allow for an elegant implementation of the strict constructors that can be user-defined in the functional language MIRANDA [S]. In MIRANDA, the programmer has control over the strictness of new user-defined constructors for abstract data types. For example, one can define strict lists and their constructors as
lisplist * : := nil 1lispcons * ! ( lisplist * ) ! . 257
Volume 24, Number 4
INFORMATION PROCESSING LETTERS
If we already have an R t combinator, the implementation of the strict lispcons in terms of the 'lazy' cons constructor that is available in the standard implementation is given by lispcons
=R< [1, 1]
chains (see [4]) in constant space. These results lend some extra appeal to the use of Turner combinators as a m e t h o d of implementing higherorder, purely applicative functional languages.
cons.
Other strict or partially strict operators can be defined (implemented) in the same way.
4. Conclusions We have introduced a strictifying combinator Re, which can be used to implement strict functions in functional languages compiled to Turner combinators. We have shown the effectiveness of its use in strictifying definitions with a large number of parameters and also an example of its use in controlling the strictness of constructors in MIRANDA.
U n d e r the assumption that this combinator is to be implemented in an architecture that would have taken it into consideration beforehand, we have stated that for definitions with a small number of parameters ( ~ 64) the implementation of strictifying combinators uses constant space. The description of the implementation m e t h o d can be found in [3]. It is of some importance that the m e t h o d can also be used to implement small BC-
258
2 March 1987
References [1] S.R.L. Meira, Optimized combinatoric code for applicative language implementation, Proc. 6th Intemat. Syrup. on Programming (Springer, Berlin/Heidelberg, 1984). [2] S.R.L. Meira, On the efficiency of applicative algorithms, Ph.D. Thesis, Rutherford College, Univ. of Kent, U.K., 1985. [3] S.R.L. Meira, Proposta de urea m~tquina de redu~o de combinadores, Rept. RT-DI-01/86, Dept. of Computer Science, UFPE, Recife, PE, Brazil, 1986, In preparation (in Portuguese). [4] K. Noshita and T. Hikita, The BC-chain method for representing combinators in linear space, New Gen. Comput. 3 (1985) 131-144. [5] H. Oberhauser and R. Wilhelm, Flow analysis in combinator implementations of functional programming languages, Rept. 04-1984, FB-10 Informatik, Univ. des Saarlandes, Saarbriicken, F.R.G., 1984. [6] D.A. Turner, Aspects of the implementation of programming languages, Ph.D. Thesis, Brasenose College, Oxford Univ., U.K., 1981. [7] D.A. Turner, Functional programs as executable specifications, Phil. Trans. Royal Soc. London 312 (1985) 363-388. [8] D.A. Turner, MIRANDA Reference Manual (Computing Lab., Univ. of Kent, Canterbury, U.K., 1985) in preparation.