I don't see anything in the index about a constant variable declaration.
If you want constants that can't be modified, here is the essence:
#lang racket
(module constants racket/base
(provide (all-defined-out))
(define const1 1)
(define const2 2))
(require 'constants)
(+ const1 const2)
#;
(set! const1 11)
If you uncomment the last line, you see that assignment is prohibited.
If you need this a lot, abstract over it with a macro.
I'm trying to do that, and for my first attempt, I wanted to just handle defining a single constant. I have:
(define-syntax (define/constant stx)
(syntax-case stx ()
[(_ constant:id value:expr)
(module constants racket/base
(provide (all-defined-out))
(define constant value))
(provide constants)]))
which is basically a copy-and-paste of the provided code. But I get
module: allowed only at the top level or in a module top-level
in: (module constants racket/base (provide ...
I tried wrapping that in a begin
, but that just complains about the provide
, which can't appear inside a begin
.
How do I construct the output of the macro here?
-
Your
:expr
notation suggests you wanted to usesyntax/parse
not `syntax/case' so I went with it. -
Defining and importing modules via syntactic sugar takes a bit of hygiene manipulation. So try this:
#lang racket
(require (for-syntax syntax/parse))
(define-syntax (define/constant stx)
(syntax-parse stx
[(_ constant:id value:expr)
#:with L (datum->syntax stx 'racket/base)
#:with R (datum->syntax stx '(require 'constants))
#`(begin
(module constants L
(provide (all-defined-out))
(define constant value))
R)]))
(define/constant x 1)
x
should not it be? 'constants
, (quote constants)
as in the previous posts?
but i read quickly and could be wrong
Thanks for the reply. I guess the principle is that anything in another module is read only. But regarding modules, what is the purpose of sticking "racket/base" after the module name "constants"? And in other languages a module provides, in addition to information hiding, a namespace that handles name clashes. But the variables are used without a module prefix. Maybe the "all-defined" statement stops a required module prefix, but is there a way in racket to use, or force the use of module names as prefixes to the module entities (like module-name.function-name in other languages)?
Yeah. I'm still fuzzy on all the macro support things. I put in the :id
and :expr
just by looking at some of the previous macros I've written.
But this doesn't seem to be defining things for me. If I run the above code, in the REPL I can't use define/constant
:
> (define/constant foo 123)
> foo
foo: undefined;
cannot reference an identifier before its definition
in module: "/path/to/source.rkt"
If I put the define/constant
in my source code, send that to the REPL, it works, but the constant...isn't. Although this appears to be the expected behavior, see below.
> foo
123
> (define foo 456)
> foo
456
For the second problem, am I correct that it's actually how define
and symbol lookup works? The
reference for namespaces says:
If an identifier is bound to syntax or to an import, then defining the identifier as a variable shadows the syntax or import in future uses of the environment."
The example in that section is very instructive, though I don't know if I still 100% understand it.
So, two questions (for now, I actually have lots more ):
- Is it possible to prevent redefinition? And if not (which is my guess), how to disambiguate shadowed symbols? I'm thinking something like
constants.foo
or similar to refer to the providing module. - Why does
define/constant
in the REPL not bind the defined constant in the REPL's scope? That seems a bit at odds with what I'm seeing from the examples in the namespaces section and top-level bindings.
-
Modules do set up a scope, i.e. a space for managing names. If your module imports from two others, both of which export
x
, Racket signals a name clash. -
There’s a sub-language for managing exports and imports. If your code should tell the reader everywhere that
x
comes from modulem
, import with(prefix-in m:
and then refer tox
asm:x
. -
The top-level REPL is not a place where you program but where you explore things:
— no,
define/constant
won’t work there the way we have set it up
— no, the secondfoo
is not the first one. So you didn’t mutatefoo
you created a new one. -
The top-level is hopeless. Matthew Flatt. (Google’s AI mode gives a nice summary.)
I was just going to post something with prefix-in
.
I think using a prefix is a nice solution to @Dashman's original question if one wants to use a macro:
;; using the '%' convention for "not the final solution" from Let Over Lambda, section 1.1:
(define-syntax (define/constant% stx)
(syntax-parse stx
[(_ constant:id value:expr)
#:with module-path (datum->syntax stx 'racket/base)
#:with require-expr (datum->syntax stx '(require (prefix-in constants: 'constants)))
#`(begin
(module constants module-path
(provide (all-defined-out))
(define constant value))
require-expr)]))
(define/constant foo "foo via define/constant")
constants:foo ;; evaluates to the above string
What I like about that is: if you need to use the prefix everywhere, it's super obvious that this value is intended to be a constant. OTOH the macro call just has foo
, so one could expect that foo
would thereafter be defined.
Now, what about defining multiple constants? The above macro is nice but if you use it twice, you get a module name clash. I looked at Greg H's "fear of macros" example where he does something very similar but:
- if we're using
prefix-in
, we don't actually care about the module's "true" name - so, let's make it something guaranteed to be unique. Oh, hello there,
gensym
...
So, here's a version that allows multiple define/constants
:
(define-syntax (define/constant stx)
(syntax-parse stx
[(_ constant:id value:expr)
#:with module-path (datum->syntax stx 'racket/base)
#:attr module-name (datum->syntax stx (gensym))
#:with require-expr
(datum->syntax stx
`(require (prefix-in constants:
(quote ,(attribute module-name)))))
#`(begin
(module module-name module-path
(provide (all-defined-out))
(define constant value))
require-expr)]))
The attr
bit and the various quoting legerdemain is a bit mysterious to me, but it does work.
From here, the only thing I'd want is a way to specify the prefix. That could be nice if constants:
is too much.
But, it is nice that since there's nothing special about a colon, you can do:
(define/constant db:thing1 "something related to the database")
(define/constant db:thing2 "another thing related to the database")
(define/constant ui:thing1 "some UI-related constant")
(list constants:db:thing1 constants:db:thing2 constants:ui:thing1)
Nice.
You want two modules: one for defining define/constant
once and for all, and another one where you use the form. That needs another "dance":
define-constant.rkt:
#lang racket
(provide define/constant)
(require (for-syntax syntax/parse))
(define-syntax (define/constant stx)
(syntax-parse stx
[(_ constant:id value:expr (~optional (~seq #:prefix p:id)))
#:with module-path (datum->syntax stx 'racket/base)
#:with require-expr
(if (attribute p)
(datum->syntax stx `(require (prefix-in ,(syntax-e #'p) ',(syntax-e #'constant))))
(datum->syntax stx `(require ',(syntax-e #'constant))))
#`(begin
(module constant racket/base
(require (prefix-in s: (only-in racket #%datum provide require)))
(require racket/match)
(s:provide constant)
(match-define `[,constant] (s:#%datum value)))
require-expr)]))
(I decided to make #:prefix an option, but not required.)
define-constant-client.rkt:
#lang racket
(require "define-constant.rkt")
(define/constant foo "foo via define/constant" #:prefix db:)
db:foo
(define/constant var "bar via define/constants")
var
(I will say I tried to get dr to pick up on these bindings but I failed.)
The approach of using a separate module is very robust and feasible to write by hand. It has complications, though, especially if the right-hand side of the definition needs to refer to other imported or locally-defined identifiers. If we are going to write a macro to implement constant definitions, I'd instead do it like this:
#lang racket
(require syntax/parse/define
(for-syntax syntax/transformer))
(define-syntax-parse-rule (define/constant name:id rhs:expr)
#:with (tmp) (generate-temporaries #'(name))
(begin
(define tmp rhs)
(define-syntax name
(make-variable-like-transformer #'tmp))))
(define/constant x 1)
(let ([y 2])
(define/constant both
(list x y))
both)
#;
(set! x 99) ; set!: cannot mutate identifier in: x
It helps that the one-argument version of make-variable-like-transformer
makes attempted assignment an error.
Good point. I reported what I have done in the past, I should have thought of the transformer approach. (Or disabling set! via transformer)
This is probably obvious, but for future readers that say "wow, Racket really hates immutability", I'd like to point out that immutability is quite straightforward if your model is that an individual module is typically written by a person who knows what's going on in that module, and that the principal challenge in maintaining immutability is that other users of the module may not.
In other words, I write code with many immutable variables. As long as I don't mutate those variables inside of my module, they will definitely remain immutable everywhere.
Naturally, this applies only to bindings of non-mutable values; if I want to prevent mutable values from being mutated, I've got a different problem.
You can’t directly modify an object defined in a different module … but you can call a function to modify it.
If you are familiar with Racket’s parameters, I have used a similar idea (sans stack) to create simple global variables that can be defined in, and exported from, separate modules.
(define-syntax get/set!
(syntax-rules ()
((get/set!)
(let [(var null)]
(lambda x
(cond
([null? x] var)
([pair? x] (set! var (car x)))
(else (error))
))
)
)))
(define my-var (get/set!))
(my-var 42) ; set it
my-var then is a closure that can be exported from its defining module. Wherever it is in scope, you can read its current value with (my-var) and modify it with (my-var ). Being untyped Racket, the value can be anything.
Perhaps not the most elegant solution, but it is simple, it works, and Racket guarantees that it is thread safe - for Racket’s green threads, anyway.
This classic pattern can be implemented more simply—no macro needed:
#lang racket
(define (get/set! [init null])
(case-lambda
[()
init]
[(new)
(set! init new)]))
(define my-var (get/set!))
(my-var 42)
(my-var) ; -> 42
Using case-lambda
dispatches on the number of arguments provided automatically and efficiently, with built-in error reporting.
I think its important to distinguish assignment to variables from mutation of values like vectors and boxes. The module system only affects assignment. (Probably my least favorite thing about the Swift programming language is its conflation of these two distinct concepts.) Concretely, if you require this module:
#lang racket
(provide bx)
(define bx
(box 1))
you can use set-box!
to change the contents of the box bx
to your heart’s content, but you can not use set!
to assign the name bx
to some other value.

In other words, I write code with many immutable variables. As long as I don't mutate those variables inside of my module, they will definitely remain immutable everywhere.
It might also be worth mentioning that Check Syntax and other tools can tell distinguish assigned variables:

Naturally, this applies only to bindings of non-mutable values; if I want to prevent mutable values from being mutated, I've got a different problem.
For this (very different) problem, contracts and lower-level impersonators/chaperones support interposing on, and indeed preventing, attempts to mutate mutable values. (Again, for posterity.)

(Probably my least favorite thing about the Swift programming language is its conflation of these two distinct concepts.)
Well I agree with you completely, but is the way Swift does it any different from the version of this problem that exists in C, Java, JavaScript, Python, and every other language on earth? Specifically, this
a = 14
... is a binding mutation, and this
a.x = 14
is the mutation of a mutable value.
Separately, and more specifically pedagogically, I'm intrigued by your use of the word "assignment" to refer to what I'm calling "binding mutation"; it would be nice to highlight the difference between binding mutation and value mutation in the classroom by using different words for them, but I'm worried that the word "assignment" would also tend to apply to the initial creation of the binding, which I think I would not want to do.

Well I agree with you completely, but is the way Swift does it any different from the version of this problem that exists in C, Java, JavaScript, Python, and every other language on earth?
My problem with Swift specifically is that it has both mutable and immutable variants of its generic collections (dictionaries, fancy arrays, etc.), and the way it decides which to create is by whether they are bound to a variable or constant.
var thisIsMutable = ["apples"]
let thisIsNot = ["bananas", "tomatoes"]
There is no way to declare a constant that will always refer to some particular mutable array (even as its contents change), nor a variable that will refer to different mutable arrays.

Separately, and more specifically pedagogically, I'm intrigued by your use of the word "assignment" to refer to what I'm calling "binding mutation"; it would be nice to highlight the difference between binding mutation and value mutation in the classroom by using different words for them, but I'm worried that the word "assignment" would also tend to apply to the initial creation of the binding, which I think I would not want to do.
It is very hard to consistently use different terms! The Racket Guide uses "assignment" to describe set!
in 4.9 Assignment: set!. Interestingly, I wouldn't tend to use "assignment" for the initial creation: I'd probably say "definition", "declaration", or even "binding".
Oog... interesting, Swift tries to give the programmer what they probably want, at the expense of further muddying the distinction between binding and value mutation, and making certain things impossible. Sigh. Did not know that, many thanks. An interesting example for the PL menagerie. There are probably a bunch of good exam questions hiding in there.
Like @LiberalArtist the way Swift handles this also annoys me.
Another Swift grievance is the way the word "closure" (or "closure expression") is used instead of "lambda expression".
Grammar of a primary expression
primary-expression → identifier generic-argument-clause?
primary-expression → literal-expression
primary-expression → conditional-expression
primary-expression → closure-expression
primary-expression → parenthesized-expression
...

While we're venting about Swift (in other ways a very nice language), I had forgotten until yesterday that the following program (from the docs) copies the array instead of aliasing. Maybe the deeper design issue is a conflation of names with values, even outside the context of mutation/assignment.
var numbers = [1, 2, 3, 4, 5]
var numbersCopy = numbers
numbers[0] = 100
print(numbers) // Prints "[100, 2, 3, 4, 5]"
print(numbersCopy) // Prints "[1, 2, 3, 4, 5]"