I recently registered ScopedSettings.
Thanks to @vchuravy we now have ScopedValues
(natively as Base.ScopedValues
since Julia v.11 and via the fallback package ScopedValues for earlier Julia versions).
ScopedValues are a powerful new way to do “dependency injection” in Julia, and handle “preferences” in a scope-dependent way. One issue though is that the global/default value of a ScopedValue
is fixed. In the past, we often used a global Ref
or so for default settings in packages. Those are easy to change/override, but don’t provide scoping. While ScopedValue
doesn’t allow for an easy global change/override.
ScopedSetting
builds on ScopedValue
to combine these two approaches: The global/default value can be manipulated/overridden (thread-safe), and it can be a fixed value or the result of a function that computes it on the fly. But in scopes, it behaves like a ScopedValue
:
using ScopedSettings
some_setting = ScopedSetting(42)
some_setting isa ScopedSetting{Int}
is accessed like a ScopedValue
some_setting[] == 42
it’s global default value can be overridden
some_setting[] = 11
some_setting[] == 11
and can also be restored to the original default value
some_setting[] = nothing
some_setting[] == 42
The global default can also be function (without arguments):
other_setting = ScopedSetting(()->rand())
[other_setting[], other_setting[], other_setting[]] # random values
other_setting[] = 1.2 # override
other_setting[] == 1.2 # no more random values
Like with a ScopedValue
, scoped settings can be set to different values for different scopes:
@with some_setting => 33 other_setting => 5.2 begin
# Within this scope, we have
some_setting[] == 33 && other_setting[] == 5.2
end
with(some_setting => 33, other_setting => 5.2) do
# Within this scope, we have
some_setting[] == 33 && other_setting[] == 5.2
end
# Globally we still have
some_setting[] == 42
other_setting[] == 1.2
ScopedSettings re-exports ScopedValues.@with
and ScopedValues.with(...)
. You can mix ScopedSetting
and ScopedValue
objects in @with
expressions
and with(...)
calls.
To base ScopedSetting
default values on package preferences and environment variables, ScopedSettings provides GetPreference{T}
function objects:
setting_foo = ScopedSetting(GetPreference(SomePackage, "foo", 42))
setting_bar = ScopedSetting(GetPreference(SomePackage, "bar", :green))
setting_foo[] == either_envvar_or_preference_value_or_42
setting_bar[] == either_envvar_or_preference_value_or_green
@with setting_foo => 11 setting_bar => :blue begin
# Different values within this scope
setting_foo[] == 11 && setting_bar[] == :blue
end
# Original values outside of the scope
setting_foo[] == either_envvar_or_preference_value_or_42
setting_bar[] == either_envvar_or_preference_value_or_green
In the global scope, the value of setting_foo[]
will depend on the LocalPreferences.toml
files (if any) in your LOAD_PATH
that have entries like
[SomePackage]
foo = 33
bar = "turquoise"
and environment variables like SOMEPACKAGEJL_FOO
and SOMEPACKAGEJL_BAR
(environment variables take precedence over preferences).