More Functional Reusability in C / C++ / Objective-c with Curried Functions



Similar documents
The C Programming Language course syllabus associate level

Lecture 1: Introduction

Chapter 1. Dr. Chris Irwin Davis Phone: (972) Office: ECSS CS-4337 Organization of Programming Languages

Programming Languages

Advanced compiler construction. General course information. Teacher & assistant. Course goals. Evaluation. Grading scheme. Michel Schinz

1.1 WHAT IS A PROGRAMMING LANGUAGE?

The Clean programming language. Group 25, Jingui Li, Daren Tuzi

Organization of Programming Languages CS320/520N. Lecture 05. Razvan C. Bunescu School of Electrical Engineering and Computer Science

Topics. Introduction. Java History CS 146. Introduction to Programming and Algorithms Module 1. Module Objectives

Object-Oriented Software Specification in Programming Language Design and Implementation

Interpreters and virtual machines. Interpreters. Interpreters. Why interpreters? Tree-based interpreters. Text-based interpreters

Introduction to Software Paradigms & Procedural Programming Paradigm

Chapter 5 Names, Bindings, Type Checking, and Scopes

Last Class: OS and Computer Architecture. Last Class: OS and Computer Architecture

Object Oriented Software Design II

Introduction to Scientific Computing Part II: C and C++ C. David Sherrill School of Chemistry and Biochemistry Georgia Institute of Technology

KITES TECHNOLOGY COURSE MODULE (C, C++, DS)

Functional Programming. Functional Programming Languages. Chapter 14. Introduction

MPLAB TM C30 Managed PSV Pointers. Beta support included with MPLAB C30 V3.00

The Technology Behind a Graphical User Interface for an Equational Reasoning Assistant

Comp 411 Principles of Programming Languages Lecture 34 Semantics of OO Languages. Corky Cartwright Swarat Chaudhuri November 30, 20111

[Refer Slide Time: 05:10]

Glossary of Object Oriented Terms

THE CERN/SL XDATAVIEWER: AN INTERACTIVE GRAPHICAL TOOL FOR DATA VISUALIZATION AND EDITING

A generic framework for game development

CSCI 3136 Principles of Programming Languages

Name: Class: Date: 9. The compiler ignores all comments they are there strictly for the convenience of anyone reading the program.

Moving from CS 61A Scheme to CS 61B Java

Variable Base Interface

Language Evaluation Criteria. Evaluation Criteria: Readability. Evaluation Criteria: Writability. ICOM 4036 Programming Languages

Embedded Programming in C/C++: Lesson-1: Programming Elements and Programming in C

Tail Recursion Without Space Leaks

Real Time Programming: Concepts

Configuration Management

Evolution of the Major Programming Languages

Objectives. Chapter 2: Operating-System Structures. Operating System Services (Cont.) Operating System Services. Operating System Services (Cont.

Introduction to programming

Data Abstraction and Hierarchy

Linux Driver Devices. Why, When, Which, How?

Storage Classes CS 110B - Rule Storage Classes Page 18-1 \handouts\storclas

Static vs. Dynamic. Lecture 10: Static Semantics Overview 1. Typical Semantic Errors: Java, C++ Typical Tasks of the Semantic Analyzer

Sources: On the Web: Slides will be available on:

Input Output. 9.1 Why an IO Module is Needed? Chapter 9

High-Level Programming Languages. Nell Dale & John Lewis (adaptation by Michael Goldwasser)

CSE 307: Principles of Programming Languages

gprof: a Call Graph Execution Profiler 1

1/20/2016 INTRODUCTION

Handout 1. Introduction to Java programming language. Java primitive types and operations. Reading keyboard Input using class Scanner.

Integrating TAU With Eclipse: A Performance Analysis System in an Integrated Development Environment

language 1 (source) compiler language 2 (target) Figure 1: Compiling a program

Functional Programming

Compiler Construction

THE IMPACT OF INHERITANCE ON SECURITY IN OBJECT-ORIENTED DATABASE SYSTEMS

A Meeting Room Scheduling Problem

Master of Sciences in Informatics Engineering Programming Paradigms 2005/2006. Final Examination. January 24 th, 2006

Bachelors of Computer Application Programming Principle & Algorithm (BCA-S102T)

On Understanding Types, Data Abstraction, and Polymorphism

Scoping (Readings 7.1,7.4,7.6) Parameter passing methods (7.5) Building symbol tables (7.6)

Semantic Analysis: Types and Type Checking

The programming language C. sws1 1

COMPARISON OF OBJECT-ORIENTED AND PROCEDURE-BASED COMPUTER LANGUAGES: CASE STUDY OF C++ PROGRAMMING

Java Application Developer Certificate Program Competencies

1 The Java Virtual Machine

Characteristics of Java (Optional) Y. Daniel Liang Supplement for Introduction to Java Programming

C Compiler Targeting the Java Virtual Machine

Crash Course in Java

CSC Software II: Principles of Programming Languages

A Java-based system support for distributed applications on the Internet

Object Oriented Software Design II

An Implementation Of Multiprocessor Linux

Chapter 2: Remote Procedure Call (RPC)

The Java Virtual Machine and Mobile Devices. John Buford, Ph.D. Oct 2003 Presented to Gordon College CS 311

BSc (Hons) Business Information Systems, BSc (Hons) Computer Science with Network Security. & BSc. (Hons.) Software Engineering


OKLAHOMA SUBJECT AREA TESTS (OSAT )

CS 241 Data Organization Coding Standards

Freescale Semiconductor, I

Parameter Passing. Parameter Passing. Parameter Passing Modes in Fortran. Parameter Passing Modes in C

What is a programming language?

Habanero Extreme Scale Software Research Project

Generalizing Overloading for C++2000

Persistent Binary Search Trees

02 B The Java Virtual Machine

System Calls and Standard I/O

Motorola 8- and 16-bit Embedded Application Binary Interface (M8/16EABI)

Programming Languages & Tools

2) Write in detail the issues in the design of code generator.

Embedded Systems. Review of ANSI C Topics. A Review of ANSI C and Considerations for Embedded C Programming. Basic features of C

System Structures. Services Interface Structure

On Understanding Types, Data Abstraction, and Polymorphism

Parallelization: Binary Tree Traversal

Simulation-Based Security with Inexhaustible Interactive Turing Machines

Memory management. Announcements. Safe user input. Function pointers. Uses of function pointers. Function pointer example

Building Applications Using Micro Focus COBOL

Lecture Data Types and Types of a Language

Transcription:

More Functional Reusability in C / C++ / Objective-c with Curried Functions WORKING PAPER Laurent Dami Abstract Several mechanisms commonly used in functional programming languages can be beneficial in terms of conciseness and reuse potential in more traditional programming areas, like applications programming or even systems programming. An implementation of functional operators for the C, C++ and Objective-C languages, based on the principle of curried functions, is proposed. Its implications in terms of improved power and additional cost are examined. Examples of parameterized function generators, function compositions and closures are given. A particular section shows how closures of C++/Objective-C objects with their member functions can be done with the currying operator. 1. Introduction Functional programming is still limited mainly to academic circles; applications programmers or systems programmers generally use imperative languages like C, PASCAL, FORTRAN, CO- BOL or ADA, or, more recently, object-oriented languages like C++, Objective-C or Smalltalk. In these languages, functional operators are rather limited, if present at all. The interest of such operators has probably been outweighed by concerns for simplicity or efficiency during the language design process. In the light of the growing concern for reusability of code, this position must be reconsidered. Several mechanisms commonly used in functional languages seem very useful for combining software components together, and seem to lead to more concise programs with a higher reusability potential. Recent approaches to building applications, in which the essential activity is no longer programming, but rather configuring and gluing together predefined components (scripting [9]), are in great need of such versatile mechanisms for making connections. This paper discusses an implementation of some functional constructs within the framework of the C programming language. C is a traditional, imperative language which is widely used for both applications and systems programming[8]. We propose to enrich C with a single mechanism inspired from the curried functions used in denotational semantics. This mechanism is relatively simple to add in a language that uses a stack for parameter passing across function calls, and it is sufficient for generating a large number of the constructs commonly used in functional programming. Several examples demonstrate the usefulness of C curried functions, in particular when interface protocols between modules exchange function parameters, like in eventdriven programming. We also explore the implications of this mechanism in the object-oriented derivatives of C, namely C++ and Objective-C.

86 Curried functions 2. Motivation 2.1 Functional constructs and reusability Functions (procedures, subroutines) are probably the most important abstraction in programming languages. The ability to group a collection of instructions under a single name, and to use that name in other places in a program, is essential for managing the complexity of large systems through different levels of abstraction. Nearly every programming language has a notion of function, procedure or subroutine. However, the allowed operations on functions largely differ from one language to the other. To make this clear, we propose a brief classification of some major programming languages, according to their flexibility in the use of functions: in COBOL or FORTRAN, functions are only denotable (they can only be named and called) in PASCAL, functions or procedures can be passed as parameters to other functions in C, function pointers can be stored and can be the result of an expression functional languages like ML, Miranda, LISP or SCHEME support function abstraction through lambda expressions. Functions that are only denotable are sufficient as long as the traditional software development scenario is respected, in which the software environment provides basic building blocks (language constructs and libraries of functions) and the programmer provides the global architecture (the main program). However, when it comes to other development scenarios where it is the system that provides the global architecture, and where application programmers provide some of the building elements, then denotable functions are not enough. Such development situations appear in most event-driven programs, particularly in windowing applications; they also belong to the notion of reusable application frames[2] that programmers can customize to suit particular needs. The problem there is that programmers must bind their own functions to the global framework, and that in most traditional languages they do not have rich possibilities for creating their own functions. Even the C language, which is quite flexible in the use of function pointers, is limited by the fact that functions can only be defined statically. Various degrees of versatility in the use of functions also have important consequences on the reusability of code because the function call is still the major mechanism associated with modularity. Even if techniques like genericity or inheritance now offer richer binding possibilities the main glue between modules is primarily the function call. Therefore an environment that supports functional operators can create more flexible binding configurations between modules, and provides more freedom for reusing modules in different contexts. Now the obvious question is: if functional operators appear to be very useful for some programming tasks, why are they not more widely used? A possible answer is that most imperative programming languages are implemented in a way which is not convenient for supporting functional operators. On the other hand, functional programming languages do not always fit the requirements for application programming, notably because of their increased needs for computing resources. Here we propose a compromise, where an application programming language is

Laurent Dami 87 extended with some functional programming features, at a cost which we consider acceptable when compared to the additional power it provides. 2.2 Why C? The choice of C as a target was very natural. C is widely used on many architectures and has important libraries that we want to be able to use. Furthermore, C already supports function pointers, which is a good basis for adding functional capabilities. Finally, C has two object-oriented derivatives (C++ and Objective-C) in which we can experiment with the interference between objects and functions. 2.3 Designing a C extension Readers of electronic news on the Internet network regularly see proposals for extending C or C++ with new features. Actually, several recent postings denoted a large interest for concepts like function composition, closures or continuations. Nevertheless, most of these proposals are quickly rejected by the C/C++ programmers community, which is not ready to pay the additional cost that these extensions would imply. In order to be successful, then, an extension proposal should be very careful about the repercussions on existing code. We designed our extension with the following points in mind: would the normal C function call protocol be affected by a new functional mechanism? to what extent would a new mechanism be compatible with existing C code (source-level/binary level)? would functional mechanisms interfere with objects in C++ or Objective-C? what is the additional cost (cpu time / memory usage) of the new mechanism? We came up with a solution that implements currying, or partial function application, for C. This gives an interesting degree of dynamic function creation, while keeping full binary compatibility with existing C code. Additionally, this mechanism costs nothing when it is not used; in other words, programmers can consciously decide in each case whether they are ready to pay additional cpu time and memory consumption for additional power. In order to support dynamic function creation without modifying the basic C function call protocol, the implementation uses run-time code creation. This approach was chosen because of its simple and clean integration with existing C code, without need for compiler modification. The drawback, however, is that a part of the implementation is machine-dependent and needs to be rewritten when ported to new architectures. Currently we have implementations of curried functions for Motorola 68020/30/40 processors and for the Sun SPARC architecture. 3. Curried functions 3.1 What are curried functions? Curried functions are named after the mathematician H. B. Curry. They are frequently used in some fields of theoretical computer science (Denotational Semantics), where among other advantages they are very convenient for reducing the plethora of parentheses in functional expres-

88 Curried functions sions. Practical use of curried functions for programming is limited to a few functional languages like ML or Miranda [3]. Curried functions are functions of more than one arguments that take one argument at a time, instead of taking all arguments together. In general, for any function f of n arguments from domains D 1 to D n, that returns a result in domain D r : f: D 1 x D 2 x... x D n -> D r there is a curried equivalent: curry f: D 1 -> (D 2 ->... ->(D n ->D r )) which is defined as (using lambda notation) curry f = λ.λa 2.....λa n.f(, a 2,..., a n ) where D i In other words, curry f is a function that takes a first argument and returns a function, which in turn takes an argument and returns another function, which continues the same process until all arguments are present, at which point the original function f is evaluated to yield a result. A standard example, borrowed from Gordon[7], is the function: plusc :Int -> Int -> Int plusc = λx.λy.x+y that yields the sum of two curried arguments (plusc stands for plus curried ). From that function, we can build new functions: succ, pred: Int -> Int succ = plusc 1 pred = plusc -1 and then evaluate these functions to get final results. succ 7 = 8 pred 0 = -1 In a theoretical context, a curried function partially applied to some of its first arguments is an expression that can be simplified by β-reduction, i.e. if all arguments are supplied, this yields a closed expression with no bound variables. However, in the context of C programming, where functions are compiled and can no longer be simplified, the behavior is slightly different. Currying a function with some of its arguments yields an intermediate structure that just stores the function address together with its partial environment. This is sometimes referred to as a partial closure.it can then be applied to the rest of the arguments, or be curried further. When all arguments have been supplied, the function can finally execute. 3.2 C interface The details of our C implementation may not be of general interest, and therefore are presented later in section 6. Here we only give the interface, in order to make the examples of the next section comprehensible. The code examples use ANSI C syntax, where function pointers can be directly applied to arguments without explicit dereferencing. Curried functions are created with the call: curry(function, nargs, arg1, arg2,...)

Laurent Dami 89 where nargs is the number of curried arguments. For example a C function like: int f(int, char, float) can be curried in the following way: g = curry(f, 2, 19, X ); then g can be used as any C function pointer; so the expression: in fact calls g (4.5) f(19, X, 4.5) Since curry expressions return function pointers that differ in no way from ordinary function pointers, which is an essential feature of our implementation, it is possible to do multiple currying: g1 = curry(f, 1, 19); g2 = curry(g1, 1, X); g2 (4.5) This code yields the same result as the previous expressions. Our current implementation uses existing C constructs to add the currying mechanism. This was much easier to do than modifying the compiler, and is sufficient to prove the interest of currying. This approach has two drawbacks, however. First, curried functions cannot be typechecked, and therefore require careful use in order to avoid errors. Second, the curry function cannot know the size of its arguments, and counts them as if they were all of the size of an integer. In consequence, arguments that are larger that an integer, like for example floating-point values, must be counted as several arguments in the nargs parameter. Again, this is very likely to yield errors. It should be well understood, however, that these problems are bound to the current implementation and would not be present if currying were supported directly in the C compiler. 4. Examples The usefulness of a currying mechanism is essentially to provide a means for dynamic function creation. Three examples demonstrate the power of dynamically defined functions in various situations. The first example uses a generic, parameterized event handler to dynamically create and register different handlers for different signals. The second example builds complex selection and comparison functions out of simple elementary functions, which can then be used in collaboration with generic sort and selection algorithms. The final example uses currying to implement other functional operators. Programmers accustomed to work in functional languages may find these examples rather trivial; nevertheless, C programmers will hopefully realize how curried functions can open a vast domain of new possibilities in their customary language. 4.1 Event handling During its computation, a program may need to be informed about events occurring in its environment, like a mouse click on a window, a timer alert, or an exception signal. The traditional

90 Curried functions way to do this is to program event handlers that are called by the operating system or the window manager whenever such events occur. Usually it is necessary to write as many different handlers as there are different kinds of events to handle. This is illustrated with the signal library function,which is used in the Unix environment for associating handlers with particular events (the signal example was chosen because of its simplicity; similar techniques are applied in other event-handling situations, like callbacks in a windowing system). Here is how a normal C program would use signal to register handlers for three different kinds of events: void sigint_handler() { fprintf(stderr, interrupt (SIGINT) ); void sigalrm_handler() { fprintf(stderr, alarm clock (SIGALRM) ); void sigwinch_handler() { fprintf(stdout, window size changed (SIGWINCH) ); main() { signal(sigint, sigint_handler); signal(sigalrm, sigalrm_handler); signal(sigwinch, sigwinch_handler); sleep(360); /* wait for signals */ Although the three handlers are very similar, they cannot be replaced by one single, parameterized handler, because the signal library function has been defined in such a way that the handlers take no parameters. So if we want different behaviors for different signals - which is the case here because we print different messages on different output streams - then we really must write one handler for each signal we want to catch. Now here is the same program with currying: void handler(char *sig, FILE *f) { fprintf(f, got signal %s\n, sig); main() { signal(sigint, curry(handler, 2, interrupt (SIGINT), stderr)); signal(sigalrm, curry(handler, 2, alarm clock(sigalrm), stderr)); signal(sigwinch, curry(handler, 2, window size changed (SIGWINCH), stdout)); sleep(360); /* wait for signals */ Each curry expression supplies different arguments to the generic handler, and the resulting closures are registered to handle different signals. Not only is the curried solution shorter and

Laurent Dami 91 more elegant; it also has the possibility of dynamically adding new handlers during program execution: int sig; char signame[50]; printf( Enter a signal number you want to catch, and its name: ); scanf( %d %s, &sig, signame); signal(sig, curry(handler, signame, stderr)); This would be impossible without the curry operator, because standard C does not have the possibility of generating a new handler at run-time. A simple variation on this example will demonstrate another use of currying, namely as a glue between library functions that otherwise would be incompatible. Suppose that, instead of printing a message, we want to put a window on the screen whenever a signal is catched. On the NeXT workstation, a window for alerting the user is created with the following call: int NXRunAlertPanel(const char *title, const char *msg, const char *defaultbutton, const char *alternatebutton, const char *otherbutton,...) Obviously this function is not appropriate for becoming a signal handler because it takes many parameters. However, currying can hide these parameters and provide a glue between signal and NXRunAlertPanel: #define sigalert(message) curry(nxrunalertpanel, 5, YOU GOT A SIGNAL, \ message, OK, NULL, NULL) main() { signal(sigint, sigalert( interrupt (SIGINT) )); signal(sigalrm, sigalert( alarm clock(sigalrm) )); signal(sigwinch, sigalert( window size changed (SIGWINCH) )); sleep(360); /* wait for signals */ The fact that library functions can be combined in unusual configurations, that were never foreseen by the original designers of these functions, clearly demonstrates how currying can improve the reusability potential of existing code. The next examples provide more insight in some configurations of functions. 4.2 Generic sort and selection Suppose we want to write a program that works with collections of words and needs to perform several sort and selection operations. The standard C library has a generic function qsort that sorts an array according to a particular comparison function that must be supplied by the caller. Similarly, we can easily write a generic selection function that takes out of a list the words that match a particular predicate supplied by the caller. Now we shall see that the curry operator opens a large range of possibilities for generating interesting predicates or comparison functions out of a small number of basic elementary functions. First let us define some useful function types: typedef int (*intfunc) (char*); typedef int (*cmpfunc) (char*, char*);

92 Curried functions typedef bool (*selfunc) (char*); Now here are some elementary functions (our building blocks ): int countcharacter(char c, char* word); /* number of occurences of c in word (obvious implementation omitted) */ extern cmpfunc strcmp; /* comparison on lexical order (C library function) */ int strcmpindirect(intfunc f, char* word1, char* word2) /* apply f to word1 and word2, and*/ { return f (word1) - f(word2); /* compare the results */ bool inintbounds(intfunc f, int min, int max, char* word) /* apply f to word, check if the result*/ { int x = f(word); return (min <= x) && (x <= max); /* is in the bounds [min.. max] */ bool inwordbounds(cmpfunc cmp, /* check if word is in the bounds */ char* minword, char* maxword, char *word) /* [minword.. maxword], according */ { /* to comparison function cmp */ return (cmp(word, minword) >= 0) && (cmp(maxword, word) >= 0); From these basic functions we can generate a number of different comparison and selection functions: cmpfunc cmplength = curry(strcmpindirect, 1, strlen); intfunc countspaces = curry(countcharacter, 1, ); intfunc countdots = curry(countcharacter, 1,. ); cmpfunc cmpspaces = curry(strcmpindirect, 1, countspaces)); selfunc containsspaces = curry(inintbounds, 3, countspaces, 1, INT_MAX); selfunc containsnospaces = curry(inintbounds, 3, countspaces, 0, 0); selfunc shortword = curry(inintbounts, 3, strlen, 1, 5); selfunc longword = curry(inintbounds, 3, strlen, 15, INT_MAX); Programmers can extend this list at will, and they can write code that generates new predicates or comparison functions at run-time. Used in conjunction with generic sort and selection algorithms, this allows one to sort or select words according to an unlimited number of criteria, like lexical order, word length, regular expression matching, or the number of occurrences of a particular character. In order to do the same thing without curry, it would be necessary to explicitly write all function combinations (cmpwordsonlength, cmpwordsonnumberofspaces), and to design global data structures for storing values like word length bounds. Needless to say, this would require a lot more programming work. 4.3 Other functional operators Currying can be used to implement other functional operators. Functional operators may be very useful for example if we want to design a mathematical package that constructs various functions and displays their graphs. Here is an example where we implement function composition and an approximation of the derivative function. Other higher-order functions could be written in a similar fashion, for example for function integration, or for a conditional function. For reasons of simplicity, the example only shows functions from float to float. typedef float (*floatfunc) (float);

Laurent Dami 93 float FuncCompose(floatFunc f, floatfunc g, float arg) /* composes two functions */ { return f(g(arg)); float FuncDerive(floatFunc f, float epsilon, float x) { return (f(x+epsilon) - f(x)) / epsilon; /* approximates the derivative if epsilon is small enough */ #define COMP(f, g) curry(funccompose, 2, f, g) #define DERIVATIVE(f) curry(funcderive, 3, f, 0.0001) /* nargs=3 because the float value counts for two args */ float Inverse(float x) {return 1/x; /* now build a composite function */ floatfunc sincos = COMP(sin, cos); /* sin and cos from the library */ floatfunc invsincos = COMP(Inverse, sincos); /* take the derivative of it */ floatfunc df = DERIVATIVE(invSinCos); /* apply it to some value */ result =df (3.14); /* or pass it to another module */ drawgraph(df, <drawing bounds>); Interactive applications that do some interpretation can benefit from such functional operators. In a typical interpreter architecture, there is a table that associates basic commands to basic functionalities of the application. With functional composition, conditional functions and other functional operators, it becomes possible to extend this table at run-time, so that users can easily extend the application with their own functions. 5. Use in Object-Oriented Languages The C language was used as a basis for the development of two object-oriented programming languages: C++[10] and Objective-C[4]. Since they are compatible with C, the curry operator is applicable in those languages as well. Merging curried functions with object-oriented concepts yields some interesting results. 5.1 C++ implementation of curried functions: easier, but less powerful Before we go to more interesting considerations, it should be mentioned that a partial implementation of curried functions can be done relatively easily in C++. It suffices to create a class that overloads the function call operator and stores the curried arguments. However this solution is not fully satisfactory, because it creates curried objects instead of curried functions. This does not make much difference as far as C++ source code is concerned, but such objects cannot be passed to other modules that expect C functions, like qsort or signal in our previous examples. Furthermore, the fact that function pointers and curried object pointers are not uniform complicates the programming task: both kinds of pointers cannot be freely exchanged, for example as nodes in an expression tree, because they have different calling protocols.

94 Curried functions 5.2 Objects can be seen as functions Currying can be applied to C++ or Objective-C objects, and yields ordinary, non-member functions out of object member functions. In many cases a uniform view of objects and functions can simplify the programming task. Besides, object-oriented features like inheritance, delegation or archiving support can be used to implement families of functions, or even persistent functions. But the most important result is that objects can then be used with other modules that do not know about object-oriented programming. For example currying offers a clean solution for merging X toolkit windowing software with C++ code and letting widgets use C++ objects as callbacks. By contrast, the solution currently used by C++/Motif programmers [5], very similar to what we describe in 5.1, is much more cumbersome. Let us consider an example of currying in C++. Suppose there is a C++ class for windows: class Window{ <instance variables> public: void open(); void close(); void move(int x, int y);... ; and suppose our application wants to bind some keys on the keyboard to some actions on windows. The code could look like this Window w1, w2; bindkey(akey0, exit); /* key 0 exits the application */ bindkey(akey1, curry(window::open, 1, &w1)); /* key 1 opens window 1 */ bindkey(akey2, curry(window::close, 1, &w1)); /* key 2 closes window 1 */ bindkey(akey3, curry(window::move, 3, &w2, 1, 0)); /* key 3 moves window 2 to the right */ bindkey(akey4, curry(window::move, 3, &w2, 0, -1)); /* key 4 moves window 2 down */ Note that with one single protocol the keys can be bound to ordinary functions (exit) or to object member functions. Besides, adding new bindings at run-time for new windows is trivial. 5.3 Dynamic class modification Objective-C In Objective-C, object closures can be achieved by currying the message dispatch function: f = curry(objc_msgsend, 2, anobject, amessage) Here f is a function pointer that in fact applies amessage to anobject. Since Objective-C keeps more information than C++ in its run-time environment, we can use dynamic function generation to get some interesting configurations. For example a class could dynamically decide to modify its method table, delegating some functionality to other objects via curried messages. The dynamic delegation would be invisible to the clients. Similarly, the interface of a class could be dynamically augmented by using functional operators to create composite methods that are added in the method table.

Laurent Dami 95 6. Implementation details The general procedure for implementing curried functions in C is relatively straightforward. When currying occurs, memory must be allocated for storing the curried function and its arguments. When a curried function is called, the first arguments that were curried need to be merged with the last arguments supplied by the caller, so as to build a complete environment for the function to execute. There is one complication, however, in that we want curried functions to look exactly like normal function pointers. This means that we cannot afford to store the function and its partial environment in two different pointers: everything must be seen as one single unit by the caller. We circumvent this problem with run-time code generation. Memory areas are allocated in which some machine instructions are stored together with curried environments. In this way, the caller of a curried function can literally branch into the data. The program counter address can be used to retrieve the curried environment, and then control is transferred to the curried function. The various steps of a curried function call will be more comprehensible with some pictures. The illustrations in this section reflect the behaviour of the 68030 implementation on Sun and NeXT workstations; our SPARC implementation is based on the same principles but is slighly more complex than what the pictures display. Notice how code implementing the currying mechanism is intercalated in such a way that neither the caller nor the curried function need to be aware that a currying mechanism is employed. 6.1 Creating a curried structure g = curry(f, i,,..., ); Registers Stack Heap SP R PC SP = Stack Pointer R = Return value PC = Program Counter i f ret i f CODE Here the curry function has been called with a function pointer and some arguments to be curried. All these parameters have been copied into a new structure allocated on the heap. Some code has also been copied at the beginning of that structure, so that the program can branch to that address. The R register, which is used to return function results, contains the address of the new structure.

96 Curried functions 6.2 Invoking a curried function g(+1,..., a n ) Registers Stack Heap SP R PC a n +1 ret i f CODE The curried function g is called with some parameters. The above picture shows the situation just before branching to g. As for any normal function call, the parameters have been pushed on the stack. The program counter points to the structure on the heap, so the next instruction to be executed will be CODE. Registers Stack Heap a n SP R PC f +1 i f CODE old SP ret CODE Now CODE was executed and the situation looks quite different. The stack has grown and the curried arguments were copied to it, so now the stack contains a complete environment for calling the function f. A new structure has been allocated on the heap, that saves the previous value of the stack pointer and the return address to the original caller. The return address that resides on the stack points to that new return structure, which means that whenever f returns it will not go directly to the original caller, but rather to CODE in the return structure, so that some cleanup operations can be performed. The program counter holds the address of the next instruction, the beginning of function f. 6.3 Returning from a curried function Registers Stack Heap SP R PC val a n +1 i f CODE old SP ret CODE

Laurent Dami 97 The function f is going to return, so the program counter contains the return address that was on the stack. As we saw before, this is the address of some cleanup code in a return structure. The R register contains the return value from function f. Registers Stack Heap SP R PC val ret a n +1 ret f size CODE What the cleanup code did was to bring the stack back to its original state and to delete the return structure. The D0 register contains the return value from function f, and the program counter will branch back to the original caller. 6.4 Comments on implementation choices Clearly the curry mechanism has some associated costs. At each creation of a curried function some memory must be allocated on the heap. Calling a curried function involves allocating a return structure, and copying arguments from the heap to the stack. Even returning from a curried function implies additional cleanup work. Although we did not run extensive tests, we can estimate a curried function call to be approximately 60 times slower than a normal function call. This figure is quite high, but it should be weighed against two factors. First, our current implementation can be significantly improved by optimizing memory allocation and getting support from the compiler (if currying is fully integrated into the language). Second, currying is not likely to be used heavily in an application, but rather marginally, only in the situations where it is convenient. Therefore the overall performance of the whole application will probably not be affected too much. Another aspect of this implementation is that it has no automatic garbage collection. Creating a curried functions is like allocating memory for a new structure or a new object: the programmer should know when a curried function is no longer used and then explicitly free the memory. Instructions to do this were omitted in the programming examples just to keep the code as short as possible. Finally, it should be mentioned that currying has the disadvantage of being dependent on the order of arguments. Not all C functions are good candidates for currying: for example the library function recmp(char* regexp, char *string), that checks if a string matches a particular regular expression, can be curried easily: f = curry(recmp, 1, aregularexpression ) f is a boolean function that compares its argument with aregularexpression, which may be very convenient; however, currying would be useless if the recmp function had been defined with the arguments in reverse order. This is not a hopeless problem, because currying could also be used to implement combinators that would permute, duplicate or cancel function arguments. However, performance degradation might then become too high to be acceptable.

98 Curried functions 7. Final considerations An extension to C/C++/Objective-C has been proposed for extending functional programming capabilities. Its usefulness was shown in several examples, and an implementation was described that should be acceptable for the C programmers community. Some drawbacks have been educed, but should in our view be outweighed by the gain in expressiveness and power. Further work should progress on two fronts. On the one hand we need to develop a more efficient implementation, and to port it on several machines in order to check whether the currying concept is as portable as the C language itself. An important addition would be to fully integrate currying within a C compiler, so that curried functions can be type-checked and can use compile-time information to generate better code. On the other hand, we are interested in exploring in more depth the possibilities and limitations of currying, especially when objects are concerned, and to compare currying with other related concepts like closures, environments or continuations. References [1] Abelson, H., and Sussman, G.J, Structure and Interpretation of Computer Programs, MIT Press, 1985. [2] M. Ader, O.M. Nierstrasz, S. McMahon, G. Müller and A-K. Pröfrock, The ITHACA Technology: A Landscape for Object-Oriented Application Development, Proceedings, Esprit 1990 Conference, pp. 31-51, Kluwer Academic Publishers, Dordrecht, NL, 1990. [3] Bird, Richard, and Wadler, Philip, Introduction to Functional Programming, Prentice Hall, 1988. [4] Cox, Brad, Object-Oriented Programming, An Evolutionary Approach, Addison-Wesley, 1987 [5] Fekete, Jean-Daniel, WWL, a Widget Wrapper Library for C++, Laboratoire de Recherche en Informatique, Orsay, 1991. [6] Field, Anthony J., and Harrison, Peter G., Functional Programming, Addison-Wesley, 1988. [7] Gordon, Michael J.C., The Denotational Description of Programming Languages, Springer Verlag, 1979. [8] Kernighan, Brian W., and Ritchie, Dennis M., The C Programming Language, Second Edition, Prentice-Hall, 1988. [9] Nierstrasz, O., Tsichritzis, D., de Mey, V. and Stadelmann, M., Objects + Scripts = Applications, in Object Composition, ed D. Tsichritzis, Université de Genève, 1991. [10] Stroustrup, Bjarne, The C++ Programming Language, Addison-Wesley, 1986.