Advanced Redex Trails:
fully-fledged tracing technology for functional programs
(project proposal)


Colin Runciman
Department of Computer Science, University of York

This is a revised version of the proposal submitted to EPSRC, the research council that is funding the project. Some minor sections giving personal or financial details, or addressing issues of EPSRC policy, have been omitted.

Summary:
In many fields, high expectations of Information Technology are limited in practice mainly by current methods of developing computer software. Declarative programming systems in general, and functional languages in particular, have an important part to play in an improved software technology. These languages free programmers from the need to express specific sequences of calculation. They also provide powerful ways of directly combining component functions. They are inherently safer than programming languages now in widespread use, and dramatically more concise.

However, the very high-level nature of functional languages poses two big problems: (1) how to turn programs into efficient computations; (2) how to trace programming errors from the faults they cause. Since the mid 1980s, problem (1) has been a popular target of research, and excellent progress has been made with it: there are now optimising compilers for functional languages. But problem (2) has received less attention, and in practical terms it remains wide open. The lack of tracing and debugging tools is a long-standing gap in functional-language technology, deterring potential users.

I therefore propose a decisive attack on the tracing problem for functional programs. My aim is to advance a successful but limited prototype, recently developed with ROPA funding, to the stage of a convincing tool for practical application. Several research problems must be solved to achieve this aim: to give just two examples, efficient low-level counterparts must be found for high-level combinators with subtle properties of abstraction, and new algorithms are needed for incremental compression of heap structures to file without disturbing lazy evaluation.

Key phrases:
declarative languages; functional programming; software development; fault-tracing tools; programming environments.

1  Context

1.1  Past Projects

The broad aim of my work in recent years has been to advance the state of the art in functional programming systems, enabling wider and more effective use to be made of this technology. I have run a series of research projects in this connection, with a mixture of government and industrial funding. The most important of these for this proposal have been:

FLARE (1991--93, IEATP, GR/F 98857 C2117)
This project aimed to demonstrate that functional programming systems could be effectively applied to a range of demanding applications. It was a collaboration between industrial and academic partners, each of whom developed applications in their own field of expertise. British Telecom managed the project. I was the technical co-ordinator and co-edited a book based on the project [runcimanwakeling95]. One important conclusion from FLARE was that the availability of profiling and tracing tools can be a critical factor in the successful use of functional programming. At York we developed novel methods for profiling heap memory [runcimanwakeling93a] and parallel evaluation [RuncimanWakeling94]. There was not time to address the more complex tracing problem, but it was put high on our list of topics for future research.

Embedding Functional Programs (1994--, Canon Research Europe)
Towards the end of FLARE, a separate PhD project at York aimed to adapt a functional language implementation for the special requirements of embedded systems, including demands for memory efficiency and a richer treatment of I/O [wallacerunciman95b]. CRE has funded a continuation of this work. The current techniques used for embedding software into devices such as typewriters and copiers are a long way from purely functional programming. But there are prototype software systems developed on large workstations in declarative languages that Canon might one day wish to embed in such office products. We have been researching extensions to functional languages and their implementations to make such embedding possible. A significant part of this work has been a novel treatment of compressed binary data [WallaceRunciman98]: this could be a useful part of our tracing technology, where one of the key problems is to reduce memory demands for trace structures representing entire computational histories.

Selective tracing of functional computations using graph reduction with redex trails (1996--97, ROPA, GR/K64334)
This 18-month project aimed to demonstrate the feasibility of a particular approach to the tracing problem by building a prototype implementation. We extended a compiled graph-reduction implementation of the functional language Haskell, transforming programs so that at run-time they could build trails of contracted redexes (intermediate expressions, re-written by appeal to definitions in the program) attaching a comprehensive derivation tree to every value [sparudruncimanplilp97]. We developed a special purpose browser for the examination of trails starting from outputs or run-time faults; we experimented with partial trails and pruning rules to increase the size of applications that could be handled [SparudRuncimanIFL97]. This ROPA project convinced us that redex trails can be made the basis of an effective tracing system, useful in real applications. The goal of the project now proposed is to fulfil this potential.

1.2  Institution

The Computer Science department at York has internationally known research groups and attracted the highest possible rating (5*) in the most recent national Research Assessment Exercise. Software technology, including compilers and related tools, is a long-standing theme in the department's work.

2  Proposed Research

2.1  Background

The tracing problem
Functional programming systems offer significant advantages over more conventional alternatives, particularly for complex symbolic applications. The abstracting power and declarative nature of functional languages, largely free from low-level concerns such as sequence of evaluation and memory management, enable computational ideas to be expressed very directly and concisely, with fewer possibilities for error.

However, this property of being abstract is two-edged. Though there are fewer classes of possible mistakes in functional programs, errors do still occur, and programmers need to trace their causes. Time and again the lack of tools for tracing and debugging has deterred software developers from using functional languages [Wadler98]. This is not simple neglect on the part of implementors; a high level of abstraction separating language from machine makes tools genuinely hard to build. A computation of a conventional imperative program is comparatively easy to trace, step by step, but a functional computation performed using a sophisticated evaluation strategy such as normal order graph reduction involves a sequence of events not explicit in the program, and often baffling to the intuition. Hence the research challenges: What is an appropriate form of trace for functional computations? How best can such traces be constructed and applied in practice?

Approaches to the problem
In the 1980s, little work was done on profiling and tracing functional computations. In the 1990s, work at York and elsewhere has delivered effective profiling techniques, leading to marked reductions in the time and space costs of many applications [RuncimanRojemoSchool96, sansompeytonjonestoplas97]. Success with the tracing problem has not come so easily: a review in a recent Australian thesis [watsonphd] notes some twenty different attempts at a solution, none of them conclusive!

Approaches broadly divide into those that base traces on a linear sequence of events, and those that represent computational history using some sort of dependency graph. Methods based on a sequence of events have had some success for `mostly functional' languages with a strictly applicative evaluation sequence; Tolmach and Appel's system for the ML language is a notable example [tolmachappeljfp95]. The benefits of the sequential approach are that a trace can be displayed as computation proceeds, and that it is easy to trade time for space: given a few check-point states stored at well-chosen intervals, any past state of computational history can be reached by re-execution.

However, the more ideal purely functional languages permit the definition of non-strict functions, implemented using the lazy evaluation strategy of call-by-need. Despite some valiant attempts [watsonphd], the evidence to date is that for such programs linear traces of the sequence of reductions are ineffective. Rather, to track faults in realistic computations, it is necessary to record a non-linear structure of dependencies between expressions.

Redex trails prototype
In our work, a network of redex trails [sparudruncimanplilp97, SparudRuncimanIFL97] represents these dependencies. Functional expressions can be viewed as trees or --- since sub-expressions can be shared and may recursively reference enclosing expressions --- as graphs. Evaluation repeatedly replaces one subgraph by another, where the reduction rules used to define replacements are derived from equations that constitute the program. At each reduction step, a redex matching the left-hand side of an equation is replaced by the corresponding instance of the right-hand side. To construct redex trails, at each reduction during the computation we arrange that from every node n of the resulting subgraph there is a link to the parent redex whose reduction caused n to be introduced. Using a special-purpose browser which reconstructs expressions in source form, the programmer can proceed backwards along the relevant trails from a fragment of output, from a run-time error, or from a point at which an apparently unproductive computation has been interrupted. Our ROPA project has shown the viability of this approach, as outlined in §1; but despite its promising results, our ROPA prototype is limited in a variety of ways, each prompting lines of further research:

  1. Performance: A program transformed to generate redex trails can run 10--20 times more slowly than the original. Research questions: Could new abstract-machine instructions provide low-level replacements for current high-level combinators to manipulate trails? How can normally compiled functions be mixed with trail-generating ones, when trail generation alters type-structure at all levels?

  2. Capacity: Trails are constructed as data structures in heap memory. Even partial redex trails are very large and current pruning strategies may discard needed information. Research questions: Could our heap compression techniques be used to good effect here? Could some form of meta-programming be used to give greater control over pruning?

  3. Transience: Because trails are built in heap memory, when a functional computation is closed the trail is lost. Research question: How can trails be archived to file incrementally as computation proceeds? (This is a delicate problem because of the relationship between trails and lazy evaluation, and the need to avoid revising the archive.)

  4. Scope: Important classes of Haskell programs cannot yet be traced. For example, I/O is very restricted, and comprehensions are traced in terms of their low-level translations. Research questions: What extensions to the current definition of redex trails best represent these forms of computation? What form of access and display are most effective for these extensions?

  5. Host: The prototype is implemented as an extension of the nhc compiler and run-time system. It cannot be used in conjunction with other implementations (eg. the optimising compiler ghc). Research questions: How can the current machinery for redex trails be re-expressed in a portable tracer? If source-to-source translation is used, could a low-level library with an interface defined in Green Card [GreenCard97] or H/Direct [HDirect98] provide the necessary extension of primitives? If so, assuming what interface to the local run-time system?

  6. User interface: The prototype trail browser was developed ad hoc. It is quite complex and using it successfully sometimes requires an implementor's insight. Research questions: How can fault-tracing be modelled as a problem-solving activity? How well do the resources provided by the trail browser fit the task? How can these and other insights be reflected in improved design, including greater support for fault-finding strategies?
One consequence of these limitations and outstanding research questions is that the prototype has had few users --- mainly the developers and their students. The obstacles to wider practical use do not seem insurmountable, but there is a lot of work to be done, requiring various skills.

Other alternatives
Though we have developed redex trails as the basis for tracing, we are aware of alternatives. Perhaps the most advanced of these is Nilsson's Freja system, based on Evaluation Dependence Trees (EDTs) [NilssonPhd98]. His EDT nodes record equivalences between expressions, where each parent equivalence is a direct consequence of the child equivalences and an equation in the program. The starting point in an EDT is not a final reduction leading to an output or point of error but the root equivalence between an original main expression and its entire computed result. Two drawbacks of the EDT approach in comparison with redex trails are that incremental archiving to file is even more difficult (Nilsson rules it out in favour of repeated execution to obtain new fragments of the EDT) and that the user has to reason about larger displayed expressions.

2.2  Programme and Methodology

Overall aim:
a solution to the problem of tracing higher-order lazy functional computations that is effective in practice, based on tools for the construction and examination of redex trails.

Primary objectives
  1. a tracing system for Haskell 98 (the nearest thing there is to a standard lazy functional language) hosted in the freely available nhc compiler;

  2. a system that incorporates a method for incremental archiving of traces to file, alleviating pressure on heap memory and increasing the returns from the overhead of generating trails;

  3. a portable variant of the tracer, based on source-to-source transformation and a support library, with a clearly-defined interface to supporting operations that any host implementation must provide; (In particular, we aim to provide a version of our tracer that works in conjunction with the widely-used ghc and hugs systems, currently being integrated with EPSRC support --- GR/L 34297.)

  4. a trace-browser designed to be an effective tool for locating faults based on a thorough analysis of the tracing task as a problem-solving activity, and of the browser display as a resource; the browser will also be equipped to record how it is used, so that data can be gathered from use in practice.
Secondary objectives
  1. to improve the speed of computations generating trails, for example by transferring the operations of trail-level function abstraction and application to a lower layer of implementation;

  2. to obtain results from a wide range of practical tests using the tracer, and hence to improve the design --- eg. to determine what strategies users choose to adopt when examining traces, and to extend the tracer with explicit support for the strategies that seem most successful;

  3. to exploit the representation of redex trails as functional data structures by devising a system for meta-programming search and display routines specific to an application.
Timeliness and novelty
The need for a tracing tool that can cope with lazy higher-order functional programs has been recognised for almost twenty years. The lack of a tracer has been a key reason for the surprisingly limited use of a programming technology with so many advantages [Wadler98]. There are now optimising compilers for languages like Haskell, and tools for the integration of functional components into larger systems. A solution to the tracing problem is overdue. It is a difficult problem to tackle comprehensively, but the redex trails approach is distinctive, and with the results already obtained in the ROPA project we are well-placed to succeed.

Methodology
A lot of work has gone into our ROPA prototype, with promising results. We should advance from the given position, not restart from scratch. The principles of the compile-time transformation, the functional representation of redex trails, and the message-passing architecture of the tracer (browser communicating with functional computation) should all be preserved. We shall also continue to work with the nhc compiler, but reducing dependencies on its internal world to increase portability. Java was used for the prototype browser, and proved quite satisfactory. Though we plan to re-think the browser design, implementation will again be in Java --- a browser that works well over the internet could prove useful when we want to support external users.

So far as practical evaluation is concerned, we do not intend to set up rigorous laboratory experiments scrutinising tracing `micro-tasks'. Such experiments would absorb too much effort. Results from actual use in practice are preferable for our purpose, but general feedback from casual use might be too vague or sparse. One kind of evaluation exercise we intend to use requires two programmers. A explains to B a correctly working program of moderate size (around 500 lines) that A wrote some time ago. B now secretly introduces several deliberate errors into the program, of a kind undetected by the compiler. Given the faulty program, A uses the tracer to locate and fix all the errors, thinking aloud as they do so. B takes notes throughout, and afterwards A and B review the exercise, recording the main issues arising.

Programme of work
Figure 1 summarises the workplan in diagrammatic form. From the start, the two researchers will engage in joint tasks combining their skills as well as solo tasks demanding their individual expertise. Brief descriptions follow for each activity.



Figure 1:

  1. Acquire skill with prototype: Both RAs need to become expert users of the prototype tracer to inform their work developing its successor. Each will also need to become familiar with components of the implementation for which they will be responsible.

  2. Tracing as a problem-solving task: Newell and Simon [NewellSimon72] pioneered ways of analysing the human information processing required to solve a variety of problems, such as chess puzzles. We will similarly analyse the activity of using the tracer to locate faults in programs. The purpose is to gain understanding of tasks that must be supported by an improved browser for redex trails, including strategies that users might in principle choose to adopt.

  3. Display as resource: The display provided by the browser is a critical resource, as typical redex trails are complex and very large. Using techniques previously applied to theorem-proving systems, we shall model display information as a resource for reasoning about programs [FieldsWrightHarrison97]. The results will inform the redesign of the browser.

  4. Lift language restrictions: The top priorities are list comprehensions and file I/O. Comprehensions require new transformation rules; file I/O requires special support from the run-time system; both require extensions to the browser interface.

  5. Incremental archiving: We hope to achieve incremental archiving by extending trail-pruning routines applied by the prototype during garbage collection. Information in sections of trail deleted from heap memory will be archived to file, with the continuing stump in the main memory trail recording an archival reference. Care will be needed to avoid any costly revision of archived values or undesirable change in the degree of evaluation. Archived trails must also be properly linked to all the other information that may be required by the browser (eg. program sources, indexed I/O transcript).

  6. Faster trail generation: Trail-manipulating combinators are introduced by transformation to replace each function abstraction and each application in the original program. In the prototype, these combinators are defined in Haskell as part of a redex-trail library. We aim to define new primitive operations, perhaps new G-machine instructions in nhc, to make reduction of these combinators much faster. However, degree of evaluation is extremely delicate, particularly in the way that saturation of function applications is handled, and some likely-looking primitives fail at this point. The ability to mix traced and untraced modules could give another route to faster generation of selective trails.

  7. Redesign browser: Based on experience using the prototype, and the results of the problem-solving and resource analyses, the trail browser will be redesigned. We shall try to minimise changes to the message-passing interface with the Haskell world. The browser will also be extended to record information about how it is used.

  8. Devise experiments and instructions: Writing instructions for the use of the tracer, including information about how to carry out systematic experiments, will be an essential prelude to external use and feedback. The exercise of writing about use of the tracer should also provoke further insights into what is needed, and ideas for development.

  9. Interim release of tracer: an important milestone. All effort will focus at this stage on pulling together the different strands of development in the first year, to provide a reliable distribution of the tracer hosted by a version of nhc. This will also be a good time to visit likely users.

  10. Concerted trials of tracer: We shall test the effectiveness of the tracer ourselves using exercises of the kind noted under `methodology'. Trials by external users will be promoted and supported by mailing lists, visits and a workshop.

  11. Analyse usage and revise models: Study of the data automatically collected by the browser, alongside reports and requests from users, will confirm or alter our design assumptions and priorities. The problem-solving and resource models will be revised, and improvements made to the trail browsing interface.

  12. Portability interface: A portable variant of the tracer must be detached from nhc in two respects. Compile-time transformations working on internal representations must be adapted to generate a fresh source. Extra run-time routines required by the tracer should be isolated, specified and defined in portable libraries (Haskell 98, Green Card or H/Direct). The portable version of the tracer must forfeit some sources of efficiency that depend on internal properties of nhc, but an optimising compiler such as ghc might amply compensate for such losses.

  13. Develop support for strategies: Typical redex trails involve a large number of interconnecting paths, and there are various plausible strategies for investigating faults of different kinds. Some of these could be directly supported by the tracer --- for example, reducing display information by blocking off parts of a trail irrelevant to a specific line of enquiry, and automatically following any unique continuations. The choice of strategies to support will be informed by discussion with experienced users, and examination of usage logs.

  14. Metaprogramming display and search: Redex trails are functional data structures so there is scope for trace-time computation over them. For example, displays of some large data structures could be vastly improved by application-specific rules --- though care must be taken to avoid forcing evaluation unnaturally. We also plan to investigate ways of encoding specialised searches over trails.

  15. Final release and documentation: A Haskell implementation incorporating the new tracer, with explanatory papers, will be made available on the internet. Another task in the closing stages will be to finish writing up results of our work for refereed publication.
Project management
A weekly project meeting will provide a regular occasion to review current progress and confirm immediate plans. With a small team, working closely together, these meetings will naturally be informal. However, written records will be kept to avoid misunderstandings or things being forgotten. Interested visitors may be invited to join these meetings, and regular meetings of the Programming Languages and Systems research group will give further opportunity for brief reports and discussion.

Once a quarter we shall review progress on the project as a whole, revising plans if necessary to secure the most important objectives. We shall also issue a short quarterly bulletin to our academic and industrial contacts, and any other interested parties.

2.3  Dissemination and exploitation

We shall seek in various ways to encourage both academic and industrial Haskell users to try our new tracer, and to participate in its further development. The plan is to release an experimental version of the tracer at the end of the first year, as the common platform for a concerted period of trial use. We shall make suitable advance announcements on the internet, seek opportunities to present our work on the tracer at recognised conferences, and also hold a one-day workshop at York. Within the UK, we may visit users to get them started. Besides establishing a mailing list for general feedback and discussion, we shall publish details of suggested forms of tracing experiment on the web.

There are several archival FTP sites for public domain implementations of functional languages. We plan to make the final results of our work freely available by placing copies of software at these sites, in addition to publishing papers.

2.4  Resources

The appropriate scale for this project has been determined by comparison with the 18-month ROPA project during which the prototype redex-trail system was developed.

People
If we are to push this tracing technology far enough to make it really effective in practice, there is a lot of work to be done on several fronts. Hence the proposal to hire a team of two post-doctoral RAs for two years.

Infrastructure and equipment
The department of Computer Science at York provides computing facilities including networking and file-service, with technician support.

Each of the RAs will need a workstation. The two most common platforms for developers and users of research languages are Suns and PCs. Of these, PCs offer better performance for the price --- and we have access to an UltraSparc compute server if a Sun platform is needed. At the time of writing £2000 buys a PC system including a 400MHz Pentium II processor, 128Mb RAM, 8.4Gb disk, 17"--19" monitor, CDROM, network and graphics cards. Suitable components will be chosen and purchased separately, and assembled by our departmental technicians.

Travel
We request funds to participate in relevant conferences. ICFP is the premier international conference in functional programming; INTERACT is an international conference with user interface design as a central interest. IFL is an established European workshop emphasising new implementation methods and practical experiments in functional programming. The costing assumes that we all attend ICFP but only two of us attend each of IFL and INTERACT.

We hope to maintain close working links with researchers in Sweden. Jan Sparud (Gothenburg) worked on the ROPA project, and we value continuing collaboration with him. Henrik Nilsson (Linköping) has developed a rival approach to functional tracing, based on Evaluation Dependence Trees. We propose to visit Sweden twice a year.

Within the UK, we have links with relevant research groups at several other universities, from Exeter to St Andrews, and with industrial R&D sites, mainly in the South. We request funds for visits to discuss the development of the tracer, and to promote its experimental use.

References

[FieldsWrightHarrison97]
B. Fields, P. Wright, and M. Harrison. Objectives, strategies and resources as design drivers. In S. Howard, J. Hammond, and G. Lindgaard, editors, Human-Computer Interaction INTERACT'97, pages 164--171. Chapman and Hall, July 1997.

[FieldsMerriam98]
R. E. Fields and N. A. Merriam. Inference and information resources: A design case study. In P. Johnson and P. Markopoulos, editors, Eurographics Workshop on Design, Specification and Verification of Interactive Systems. Springer, 1998. In press.

[HDirect98]
S. Finne, D. Leijen, E. Meijer, and S. L. Peyton Jones. H/Direct: a binary foreign language interface for Haskell. In Proc. 3rd ACM International Conference on Functional Programming (ICFP'98), pages 153--162. ACM Press, September 1998.

[NewellSimon72]
A Newell and H. A. Simon. Human Problem Solving. Prentice-Hall, 1972.

[NilssonPhd98]
H. Nilsson. Declarative debugging for lazy functional languages. PhD thesis, Linköping, Sweden, 1998.

[GreenCard97]
S. L. Peyton Jones, T. Nordin, and A. Reid. Green Card: a foreign language interface for Haskell. In Proc. ACM SIGPLAN Haskell Workshop, Amsterdam, June 1997.

[rojemofpca95]
N. Röjemo. Highlights from nhc --- a space-efficient Haskell compiler. In Proc. 7th Intl. Conf. on Functional Programming Languages and Computer Architecture (FPCA'95), pages 282--292. ACM Press, June 1995.

[RuncimanRojemoSchool96]
C. Runciman and N. Röjemo. Heap profiling for space efficiency. In J. Launchbury, E. Meijer, and T. Sheard, editors, 2nd Intl. School on Advanced Functional Programming, pages 159--183, Olympia, WA, August 1996. Springer LNCS Vol. 1129.

[runcimanwakeling93a]
C. Runciman and D. Wakeling. Heap profiling of lazy functional programs. Journal of Functional Programming, 3(2):217--245, April 1993.

[RuncimanWakeling94]
C. Runciman and D. Wakeling. Profiling parallel functional computations (without parallel machines). In J.T. O'Donnell and K. Hammond, editors, Proc. 1993 Glasgow Workshop on Functional Programming, pages 236--251. Springer-Verlag Workshops in Computing, 1994.

[runcimanwakeling95]
C. Runciman and D. Wakeling, editors. Applications of functional programming. UCL Press, 1995.

[sansompeytonjonestoplas97]
P. M. Sansom and S. L. Peyton Jones. Formally based profiling for higher-order functional languages. ACM Transactions on Programming Languages and Systems, 19(2):334--85, March 1997.

[SparudRuncimanIFL97]
J. Sparud and C. Runciman. Complete and partial redex trails of functional computations. In C. Clack, K. Hammond, and T. Davie, editors, Selected papers from 9th Intl. Workshop on the Implementation of Functional Languages (IFL'97), pages 160--177. Springer LNCS Vol. 1467, September 1997.

[sparudruncimanplilp97]
J. Sparud and C. Runciman. Tracing lazy functional computations using redex trails. In H. Glaser, P. Hartel, and H. Kuchen, editors, Proc. 9th Intl. Symposium on Programming Languages, Implementations, Logics and Programs (PLILP'97), pages 291--308. Springer LNCS Vol. 1292, September 1997.

[tolmachappeljfp95]
A. Tolmach and A. W. Appel. A debugger for Standard ML. Journal of Functional Programming, 5(2):155--200, April 1995.

[Wadler98]
P. Wadler. Why no one uses functional languages. ACM SIGPLAN Notices, 33(8):23--27, August 1998.

[wallacerunciman95b]
M. Wallace and C. Runciman. Lambdas in the Liftshaft --- functional programming and an embedded architecture. In Proc. 7th Intl. Conf. on Functional Programming Languages and Computer Architecture (FPCA'95), pages 249--258, La Jolla, June 1995. ACM Press.

[WallaceRunciman98]
M. Wallace and C. Runciman. The bits between the lambdas: Binary data in a lazy functional language. In Proc. Intl. Symp. on Memory Management, pages 107--117, Vancouver, Canada, October 1998. ACM Press.

[watsonphd]
R. D. Watson. Tracing Lazy Evaluation by Program Transformation. PhD thesis, Southern Cross, Australia, October 1996.

This document was translated from LATEX by HEVEA.