An identifier is a name that appears in a program. The meaning of the name is determined by the identifier’s binding, which is an association between an identifier and some other item—e.g., a specific value, a function, a macro, or another identifier.
An identifier with a binding is also known as a variable:
1 2 | (define prometheus "I am bound, ergo I am a variable") prometheus |
1 | "I am bound, ergo I am a variable" |
An identifier without a binding is called an unbound identifier. A Racket program cannot run unless every identifier has a binding, so this is a phrase commonly seen in error messages:
1 | atlas
|
1 | atlas: unbound identifier in module in: atlas |
An identifier can be written with any Unicode characters, except whitespace and the following reserved characters:
1 | ( ) [ ] { } " , ' ` ; # | \ |
Numbers can’t be used as identifiers either:
By convention, identifiers are typically written in lowercase, with hyphens between words:
1 2 | lowercased lowercased-with-hyphens |
Naming styles common in other languages are not syntactically wrong in Racket, but they’re also not idiomatic:
1 2 3 4 | Capitalized ; no lowercased_with_underscores ; no Title-Cased ; no CamelCased ; hell no |
Certain classes of functions have recommended naming conventions. See functions.
Identifier bindings can be created three ways:
With an explicit binding form like define, let, or lambda. These bindings will override (aka shadow) existing bindings outside the form:
By importing bindings from another module with a form like require. Bindings within the module will shadow imported bindings:
But an error will arise if two imported modules provide bindings for the same identifier:
By making new bindings with a macro. Macros can attach bindings to existing identifiers, or create a new identifier and its binding.
1 2 3 4 5 6 7 8 9 10 | (define-macro (define-as-42 ID) #'(define ID 42)) (define-as-42 foo) foo ; 42 (define-macro (define-bar-as-42) (with-pattern ([BAR-ID (datum->syntax caller-stx 'bar)]) #'(define BAR-ID 42))) (define-bar-as-42) bar ; 42 |
Every identifier binding has a scope that determines where in the program the binding is visible. Outside that scope, the binding won’t work. The scope of a binding depends on how & where the identifier is introduced. The three most common options:
Expression scope: a binding created within another expression is only visible within that expression (and it can be shadowed within that expression):
Module scope: a binding created at the top level of a module with define, or imported with require, is visible throughout the module (though it can be shadowed elsewhere). + True, a module is itself an expression. But module scope is not a special case of expression scope, because bindings can only cross module boundaries when explicitly requested (for instance, with require).
Macro scope: if a macro creates an identifier and its binding (i.e., a variable) it’s only visible to other code created by the same macro. Caution: this is a trap for the unwary. In the example below, even though the macro emits (define foo 42)—which sure looks like it will create a variable foo that has module scope—it doesn’t. That variable is not visible to code outside the macro:
1 2 3 4 5 6 7 | (define-macro (make-macro-scoped-foo) #'(begin (define foo 42) (* foo 2))) (make-macro-scoped-foo) ; 84 foo ; unbound identifier (!) |
But if a macro adds a binding to an existing identifier—for instance, one passed as an argument to the macro—that binding does not have macro scope. Instead, it adopts the scope of the identifier:
1 2 3 4 5 6 7 8 9 10 11 12 | (define-macro (bind-existing-identifier ID) #'(begin (define ID 42) (* ID 2))) (bind-existing-identifier foo) ; 84 foo ; 42 (and `foo` has module scope) (let () (bind-existing-identifier bar) bar) ; 42 (and `bar` has expression scope) bar ; unbound identifier |
For more about this behavior—which is a feature, not a bug—see hygiene.
Identifiers and Binding in the Racket Guide
Identifiers, Binding, and Scopes in the Racket Reference