A function is a set of expressions that’s only evaluated when explicitly invoked at run time. Optionally, a function can take arguments as input, and return values as output. Within a program, functions are used for three overlapping purposes:
To delay the evaluation of a set of expressions.
To allow repeated evaluation of a set of expressions.
To generalize a set of expressions by using input arguments.
Functions can be passed around Racket programs like any other value:
If a function is created with define, it uses an identifier for its name. A function can also be created without a name (that is, as an anonymous function) using lambda, which can be spelled λ. A lambda expression can appear anywhere that a function name is allowed:
A lambda expression can also be bound (assigned) to an identifier. The two definitions below are equivalent (in fact, under the hood, Racket rewrites the first style of define in the lambda style):
In the Racket documentation, functions are also called procedures. A function that takes one argument and returns a Boolean (like even? or empty?) is often called a predicate. You can test whether a value is a function with the procedure? predicate.
Lambda is sometimes used generically as a synonym for function, due to Racket’s association with the lambda calculus. A lambda without arguments that wraps an expression so it can be evaluated later is sometimes called a thunk.
A function can be invoked in two ways:
Directly in an expression. Any code written as a parenthesized expression like (func arg1 arg2 ...) will be treated as a prefix-notation function call, where func is the function name and arg1 arg2 ... are passed as its arguments.
Though prefix notation is most common, Racket supports a limited form of infix notation in function expressions. You can put the function name between dots, and regardless of where it appears, it will be treated as the first element of the expression:
Indirectly, by passing it as an argument to another function. For example, apply takes a function and a list of values like (list arg1 arg2 ...) and passes those values as arguments to the function:
For another example, filter takes two arguments—a function and a list of values—and applies the function to each value, generating a new list without the values that return #f:
As mentioned above, you can always use a lambda expression in place of a function name:
A function can be defined to accept any number of arguments, including zero. This number is the arity of the function—a fancy word worth remembering, because you’ll see it in error messages:
By default, all function arguments are positional arguments, meaning they’re bound to identifiers within the function according to where they appear in the argument list. Racket also supports named keyword arguments that can appear anywhere in the argument list:
Any positional or keyword argument can have a default value by specifying that value in square brackets (as usual, arguments without default values must come first):
Functions can also be defined to take any number of arguments with a rest argument, which means “put the rest of the arguments here”. A rest argument always ends up holding a list. A rest argument can be combined with other argument types, but must appear last, and cannot have a default value:
In Racket, it’s common to use rest arguments when a function can be logically extended to take any number of arguments—even for operations that in other languages take only two arguments:
If a declared rest argument is unneeded, it becomes the empty list:
Every function has a return value. The value returned is the last value that appears in the function. There is no return statement.
If the function doesn’t have an explicit last value, then the constant #<void> is returned, which can be tested with void?. So #<void> is both a nothing and a something:
It’s rarely necessary, but a function can return multiple values, using values. These functions must be called from program positions where multiple values are accepted (for instance, with define-values rather than define):
For this reason, if you’re thinking of using values, consider first whether you could plausibly return your results inside a single pair or list or structure type, as those tend to be more convenient.
Within the Racket library, certain sets of functions have special naming conventions. Adopting these conventions for your own code isn’t mandatory, but knowing them will help you understand existing Racket code, and using them will help others understand yours:
A predicate—a function that tests membership in a certain set of items—ends with ?. For instance: string?, list?, empty?, number?.
A function that changes (aka mutates) the value of an existing variable or data structure ends with !. For instance: set!, vector-fill!, read-bytes!, hash-set!. Conversely, a mutator function with a functional variant is named without the !, for instance hash-set.
If a function handles one input argument or returns one value, a variant function that handles multiple input arguments or multiple return values uses the same name, but suffixed with *. For instance: regexp-match vs. regexp-match*, string-append vs. string-append*, list vs. list*. The * suffix can also denote a variant that treats multiple arguments as nested rather than parallel: for vs. for*, let vs. let*.
A variant function with a narrower field of operation uses a / with a qualifier suffix. For instance: define vs. define/contract, equal? vs. equal?/recur, let vs. let/cc.
Functions that convert from one data type to another have an infixed ->. For instance: string->number, char->integer, datum->syntax. + This convention is unrelated to the use of -> in the contract system.
Parameters are often named with a current- prefix, like current-directory, current-input-port, and current-namespace.
Racket belongs to a category of languages—also including Scheme, Common Lisp, and Haskell—that are associated with functional programming. Functional programming is a style of programming where functions receive certain data as input, process only that data, and return a result. (It does not mean “programming with functions”—everyone does that.)
In functional programming, programmers avoid two habits common in other languages: mutation (= changing data in-place rather than returning a value) and relying on state (= extra context that’s not provided as input, for instance global variables).
Without state and mutation, the behavior of each function is contained. Thus, it’s easier to prove things about that behavior—in the research sense (if you’re a computer scientist) or in the testing sense (if you’re a programmer).
That said, there are times where the functional-programming approach doesn’t fit, especially with functions that are used for their side effects—for instance, println. So the rule of thumb in Racket is to use functional programming when you can, and depart from it when you must.
Function Calls in the Racket Guide
Function Shorthand in the Racket Guide
Procedures in the Racket Reference
Parameters in the Racket Guide