Right now, parameters that depend on other parameters are a big weakness for appcli. For example, consider an object with attributes a, b, and c, where c is derived from a and b via f(a, b) and f is expensive. For a normal object, it'd make sense to calculate c each time a or b was set. Although this snippet is somewhat long, it's simple and robust:
This pattern isn't compatible with appcli, though. The reason is that appcli parameters are basically equivalent to the getters above, and there are no equivalents to the setters. In other words, appcli doesn't provide a way to call setters like those in the above examples. That forces the user to avoid setters and do everything with getters. This means either (i) recalculate c every time or (ii) implement some sort of caching scheme:
# Simple but inefficient:
@autoprop
class Obj:
a = appcli.param()
b = appcli.param()
def get_c(self):
return f(self.a, self.b)
# Efficient but complex, especially if multiple caches are needed.
# Unit tests would be advised, because nasty bugs are definitely possible.
@autoprop
class Obj:
a = appcli.param()
b = appcli.param()
def get_c(self):
if self._cache != (self.a, self.b):
self._c = f(self.a, self.b)
self._cache = (self.a, self.b)
return self._c
I think it'd be better if appcli had the equivalent of a setter. More specifically, this would be a callback invoked after parameters are set by init() or load(). This might look like:
@autoprop
class Obj:
a = appcli.param()
b = appcli.param()
def __load__(self):
self.c = f(self.a, self.b)
Some thoughts:
This wouldn't work for dynamic parameters. But parameters are meant to be static, and most will be. This ties in nicely with #2.
Per-config callbacks. If some parameters can only be set by some configs (e.g. command-line args but not config files), it makes sense to use different callbacks for each config. That way the callbacks can avoid trying to use parameters that haven't been specified yet. Some example syntax:
class Obj:
__config__ = [
DummyConfig().set(on_load='__load_dummy__'),
]
Per-parameter callbacks. The __load__ snippet above doesn't actually work right, because c wouldn't be recalculated if the user sets a or b in python. This syntax would solve that problem:
class Obj:
a = appcli.param(on_set=_calc_c)
b = appcli.param(on_set=_calc_c)
def _calc_c(self):
self.c = f(self.a, self.b)
I gave some thought to whether appcli.param should try to fully mimic the @property API. But I don't think it really makes sense. Properties can do absolutely anything, while parameters are meant to represent a single value that can be loaded from some configuration source. So I think it makes sense for appcli.param to only support a simple callback that can react to the value being set, but not actually change what "being set" means.
It might be possible for init() and load() to figure out which callbacks to invoke just from the parameters, without needing to have per-config callbacks.
Pseudo-code:
Load layers as usual.
Keep track of which layers are new.
Get list of params
Iterate params with on_set callbacks
If param would use one of the new layers:
Add on_set to list/set of callbacks to invoke
Invoke non-duplicate callbacks.
This would be a fairly significant change, but I think it could really be worth it.
Right now, parameters that depend on other parameters are a big weakness for
appcli
. For example, consider an object with attributesa
,b
, andc
, wherec
is derived froma
andb
viaf(a, b)
andf
is expensive. For a normal object, it'd make sense to calculatec
each timea
orb
was set. Although this snippet is somewhat long, it's simple and robust:This pattern isn't compatible with
appcli
, though. The reason is thatappcli
parameters are basically equivalent to the getters above, and there are no equivalents to the setters. In other words,appcli
doesn't provide a way to call setters like those in the above examples. That forces the user to avoid setters and do everything with getters. This means either (i) recalculatec
every time or (ii) implement some sort of caching scheme:I think it'd be better if
appcli
had the equivalent of a setter. More specifically, this would be a callback invoked after parameters are set byinit()
orload()
. This might look like:Some thoughts:
Per-parameter callbacks. The
__load__
snippet above doesn't actually work right, becausec
wouldn't be recalculated if the user setsa
orb
in python. This syntax would solve that problem:I gave some thought to whether
appcli.param
should try to fully mimic the@property
API. But I don't think it really makes sense. Properties can do absolutely anything, while parameters are meant to represent a single value that can be loaded from some configuration source. So I think it makes sense forappcli.param
to only support a simple callback that can react to the value being set, but not actually change what "being set" means.It might be possible for
init()
andload()
to figure out which callbacks to invoke just from the parameters, without needing to have per-config callbacks.Pseudo-code:
on_set
callbackson_set
to list/set of callbacks to invokeThis would be a fairly significant change, but I think it could really be worth it.