On Application
posted by Jack Firth
Today I wanted to write about function application. Specifically, how to redefine and customize application with Racket. We’ll also look at some Racket packages that define interesting and useful forms of function application.
The theory
Application is the process of combining a function and arguments to evaluate a result. Application defines one half of lambda calculus, the formal model underlying much of modern functional programming. The other half is abstraction, which is creating new functions. Creating and applying functions is the heart of Racket and many other functional languages.
So how are functions applied in Racket? What makes (if (< 5 10) 'foo 'bar)
a macro use and (< 5 10)
a function use?
That’s actually a trick question, because function application is a macro in Racket. During macro expansion, the Racket expander inspects the first element of an expression to determine if it has a binding to a macro. If it doesn’t, rather than assume the expression is a function application, the expander inserts an artificial identifier named #%app
into the expression. So in the above example, the expression (< 5 10)
is converted to (#%app < 5 10)
. This #%app
identifier doesn’t refer to a single specific #%app
like the if
refers to if
from racket/base
, rather it refers to whatever the enclosing environment defines #%app
to be (which by default means ordinary function application from racket/base
).
However, imported modules can provide their own definitions of function application by providing an #%app
macro. Let’s define our own #%app
that, in addition to applying a function, prints out a trace message. First let’s define a helper function to implement the tracing:
We use ~v
to more clearly see the difference between strings, symbols, and numbers. Now, we can write a macro to insert a call to our function wherever we use the macro:
#lang racket (define-syntax-rule (trace-app f arg ...) (trace f arg ...))
This macro is very simple, and on it’s own isn’t really useful at all (we could just call trace
directly). However, we can provide this macro with the name #%app
to trigger the automatic use of trace-app
whenever a function call is written. We’ll move our trace
and trace-app
definitions into a submodule to see this in action without multiple files:
Now, with a simple require
statement and these seven lines of code, we can trace the order of evaluation of all expressions in a Racket module.
> (require 'trace) > (+ (string-length "racket") (string-length "application")) Applying #<procedure:string-length> to arguments '("racket") Applying #<procedure:string-length> to arguments '("application") Applying #<procedure:+> to arguments '(6 11) 17
The practice
Redefinition of #%app
is occasionally used when defining new languages, but a much more pedestrian use is to add some notational shorthand to make certain constructs more convenient. For example, consider the fancy-app
package. This package provides an #%app
macro that behaves just like normal function application unless one or more underscores are used. In that case, the function application is converted to a lambda that takes as input one argument for each underscore. For example, (format "Hello ~a" _)
is equivalent to (lambda (v) (format "Hello ~a" v))
. This is especially useful for whipping up quick lambdas as arguments to functions like map
and filter
:
The rackjure
package redefines #%app
to make working with nested dictionaries easier. Dictionaries can be used to get and set values for keys when used as procedures, and when dictionaries are the second value of a function application the first value is interpreted as a key and the dictionary’s associated value is looked up. See the rackjure
documentation for details.