Is there such thing as setting a value as a constant once in the app, or otherwise allowing it to be overridden but still be considered a constant, in a statically typed language? I am imagining dependency injection, how to make it statically typed when the thing being injected is external to where its being injected.
I am imagining like a logger, setting it globally at application initialization like Rails.logger = Logger.STDOUT sort of thing, but have the Rails.logger be considered a constant throughout the code.
How would that work in an arbitrary statically typed language? The problem I'm seeing is that the dependency injected logger variable isn't a constant, since it is set at runtime, but it is basically a constant once set, for all intents and purposes. So is there a way to treat it as a constant everywhere in the code, but be able to set it somewhere externally? How would that look in some pseudocode for an arbitrary (even made-up) language?
Similar other things exist such as setting the adapter for an ORM to a specific database like Postgres, or a renderer for a UI library, etc..
I am working on a custom statically typed programming language (long ways away), and am looking for the key patterns related to dependency injection for setting "global constants" in some ideal sort of way.
So basically, how could you craft (in pseudocode or otherwise a description) a way of handling the structures so the logger was like a static constant? Where the generic logger implementation code is implemented internally in X library, and the specific instance of the logger is implemented in Y application, and injected into the library.
What I'm imagining doing (JavaScript pseudocode) is this basically:
// log-library.js
let LOGGER
export function setLogger(logger) {
LOGGER = logger;
}
export function trace(message) {
LOGGER.trace(message)
}
export function warn(message) {
LOGGER.warn(message)
}
// app-config.js
import { setLogger } from 'log-library'
setLogger({
warn: (message) {
console.warn(message)
},
trace: (message) {
console.log(message)
}
})
// hello-world.js
import './app-config' // bootstrap
import { warn } from 'log-library'
warn('foo')
But this sort of thing doesn't have the static guarantees that I am imagining.
Can an arbitrary type system / programming language make it so the LOGGER is somehow considered a constant after it is set, perhaps statically / at compile time?
Perhaps something more like this could be implementable, that's where I'm coming from.
// log-library.js
let LOGGER
export function setLogger(logger) {
LOGGER = logger;
constantify LOGGER // COMPILER DIRECTIVE?
}
export function trace(message) {
LOGGER.trace(message)
}
export function warn(message) {
LOGGER.warn(message)
}
If that sort of thing is doable, what should I make sure to take into consideration on implementation in the compiler?
I think the challenge with something like this is not the type system, but the fact that large programs typically involve large numbers of modules that are often developed independently, and while each module can have its own initialization logic, there isn't usually a clear program-wide order of initialization.
Within a single module, it's not hard to enforce a rule that a constant belonging to that module must be initialized exactly once by that module; but it's trickier to enforce a rule that a program-wide constant must be initialized exactly once in the entire program.
In a language where all modules in a program are known at the start of execution (or where you're OK with imposing additional constraints on any modules that are detected later during runtime), one approach would be to track, for each module, which program-wide constants it initializes and which program-wide constants it depends on. That makes it straightforward to enforce that every constant is initialized exactly once (or at most once, if a default/fallback value is available), and to ensure that modules are initialized in a valid order (or to reject the program upfront if no valid order exists).