05 Feb 2017

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:

#lang racket
(define (trace f . args)
  (printf "Applying ~v to arguments ~v\n" f args)
  (apply f args))

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:

#lang racket
(module trace racket
  (define (trace f . args)
    (printf "Applying ~v to arguments ~v\n" f args)
    (apply f args))
  (define-syntax-rule (trace-app f arg ...)
    (trace f arg ...))
  (provide (rename-out [trace-app #%app])))

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)

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:

> (require fancy-app)
> (map (format "Hello ~a, how are you today?" _)
       (list "Alice" "Bob" "Eve"))
'("Hello Alice, how are you today?"
  "Hello Bob, how are you today?"
  "Hello Eve, how are you today?")

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.

Made with Frog, a static-blog generator written in Racket.
Source code for this blog.