Parameters can be either parameterized or directly assigned to. The latter mutates a thread cell, while the former attaches a continuation mark.
When is it useful to actually assign a parameter instead of using parameterize? The two use cases I can think of are:
When you want (require somelib) to trigger configuration of a parameter
When you don't feel like indenting your code to the right more
These use cases don't seem super compelling on their own. I think the former could be replaced by parameterizing the body of client modules instead. Are there other reasons to directly assign parameters?
For context, I've been thinking about making something like async-await for Racket, and I want it to cooperate with parameters and continuation marks. So I'm doing a lot of research into the evaluation model for continuations and threads.
I have some html rendering code that uses a mix of parameters with parameterize as well as regular variables. I use parameterize when I can to hold the current style when the renderer descends into a new element. For other cases it wasn't convenient to use parameterize, so I used a standard variable with assignment instead. I can imagine someone choosing a slightly different path and using a mix of parameterize and assignment to parameters.
The docs on command line parsing use parameters to hold the flag values. So maybe a good question is, why do the docs use parameters here (without parameterize)?
Incidentally, I adopted this pattern in the cli library as well, so that flags correspond to parameters, but I don't recall now whether I considered using mutable (lexically-scoped / global) hashes.
Another place I've personally used parameters is in the probe debugger for Qi. This comment explains how parameters are used. Would parameterize work for this usecase? I'll plan to take a closer look at this at some point.
"Avoiding indent" seems easy to dismiss, but given the amount of effort that's gone into internal define as an alternative to let, I wonder if a "post-mortem" discussion of that effort might be relevant for this?
Given that parameters are thread-local, the ability to set that thread-local default --- the value to be used in a thread if no one parameterizes it --- seems useful? It seems to enable expressing, "If no one has some better idea, here's what I think the value should be". Maybe as you say all such uses could be replaced by a parameterization introduced in the expansion of the module... but I wonder if that option is always available or practical?
In the contrived example I gave above, the value contained by the parameter is immutable. I am definitely not a Racket expert so maybe my mental model is wrong. But if I “update” an immutable hash table contained in a parameter by doing this:
…then I’m not mutating a value held in the parameter, I’m rather changing the current thread’s copy of the parameter to point to a completely new hash table, right? Or no?
In other words, it’s still thread safe, because if I had spawned another thread before doing that update, that thread’s current-param parameter would still be hanging onto the original hash table. Using a parameter in this way prevents me from stomping on another thread’s data, where hash-set! definitely would not give me that assurance. [edit to add:] For sure it makes sense to me that it doesn’t make much sense to try and clothe a mutable value in a parameter, at least in any context I can think of.
I’ve run into this issue directly with Pollen projects. Pollen holds metadata about the current document (if there is one) in an immutable hash table served by a current-metas parameter, because it’s useful to be able to get at that data from everywhere. But it’s also useful to be able to add to or update those values from functions called within the document itself.
You don’t want the scope of those changes to be limited to that one function call, you want them to be visible to all later code evaluated in the Pollen program/document.
You also need to consider that multiple Pollen programs/documents may be running concurrently.
So you need a) an immutable hash-table contained in b) a parameter, and c) a way to update it in a way that “sticks” outside the current lexical scope (direct assignment).
I've asked myself the same question. One possibility is that you can parameterize the setting in other places in the code, but that could be very brittle depending on how the parameter or even values derived from it are used in the code.
Another possibility could be that it's easier to write (my-argument new-value)
than (set! my-argument new-value).
On the other hand, (define my-argument initial-value)
is easier to write than (define my-argument (make-parameter initial-value))
I guess the actual answer has to come from the person who wrote the command-line example.
For one, just using straightforward define and set! would avoid wondering about why the author of the code used parameters. For that reason, I think using define and set! is clearer.
Initially, one can simply follow the example and use the parameters, than, once their program grows to multiple files, they can move each parameter to the most appropriate file in their project and things will continue to work.
If you use define and set!, you'll be in for a surprise when you refactor your code.
There are, of course, several ways to solve the multi-file project problem, but I am not sure if any alternative would be clearer or simpler.
Usually, I try to avoid global state (including parameters) for command line arguments. I use one module that parses the command line data and returns it as a struct.
An example is here. I use define and set! only locally in the function make-command-line-data. The main program in todoreport.rkt gets the command line arguments with make-command-line-data and calls the actual functionality in other modules. No code accesses global state with command line data.
I try not to pass the command line struct to code that's unrelated to command line processing. Called functions are supposed to only get the data they need.
Of course, the above approach may not work or not work so easily for some other programs. That said, if in doubt I would start with a relatively simple design and refactor later if the simpler design is no longer enough (YAGNI). (You could argue that just using parameters would be simpler, but not depending on global state was so important to me that I didn't use this design. )
EDIT for p.s.: I apologize for being a smarty-pants. I realize that the fact that Racket originally provides this information as a parameter, doesn't really have any bearing on how you use it and treat it thereafter in your program.