Skip to content

Instantly share code, notes, and snippets.

@danking
Last active April 1, 2016 13:02
Show Gist options
  • Save danking/a17b3d5b75ad3b7fb1c78f7bd81690cf to your computer and use it in GitHub Desktop.
Save danking/a17b3d5b75ad3b7fb1c78f7bd81690cf to your computer and use it in GitHub Desktop.

I was thinking about nulls in configuration files. Part of the problem is that you cannot use Optional<T> in the configuration definition, so all of your configuration is implicitly optional (i.e. it could be null). It would be nice to have missing fields be treated as a configuration-time error. If we wanted optional ones, we would explicitly state Optional<ElasticSearchConfig>. Of course, there are also some cases where you actually want an arbitrary set of completely optional parameters (HTTP clients come to mind). In this case I guess you'd have a lot of Optional<T>, but it's worth it, because you won't accidentally assume that its non-optional.

Below is how I'd do the specific problem of a Logger with an optional configuration file. I assume our configuration utility is sophisticated enough to give us Optional<T>.

Notice that ILogger implements a monoidal structure: it has a plus, a zero and satisfies the law ∀x. x.plus(zero) = x. This makes it easy to expand logging functionality in a clean way. You just plus a bunch of untangled logger logic together into one. This architecture garuntees you won't have interference between the loggers (modulo exceptions, etc.).

It's also nice that the configuration gives you a maybeEsc directly so there's no way you can accidentally forget that a parameter is optional (because the type system will complain that you have an Optional<ElasticSearchConfig> not an ElasticSearchConfig).

class ESLogger implements ILogger {
public ESLogger(ElasticSearchConfig esc) {
//...
}
public static ILogger fromConfig(Optional<ElasticSearchConfig> esc) {
return esc
.map(ESLogger::new)
.orElse(ILogger.zero());
}
public debug(String fmt, Object args...) {
// ...
}
// ...
}
interface ILogger {
void debug(String fmt, Object args...);
void info(String fmt, Object args...);
void error(String fmt, Object args...);
// ...
// We can smash two loggers together
default ILogger plus(ILogger that) {
return new ILogger() {
void debug(String fmt, Object args...) {
this.debug(fmt, args);
that.debug(fmt, args);
}
// ...
}
}
// Or we could have the do-nothing logger
default ILogger zero() {
return new ILogger() {
void debug(String fmt, Object args...) { }
// ...
}
}
}
// ... not sure the best place to put setup logic but:
Optional<ElasticSearchConfig> esc = // ...
ILogger theLogger = logToFileLogger.plus(ESLogger.fromConfig(esc));
@danking
Copy link
Author

danking commented Apr 1, 2016

In fact, the ILogger interface is actually a commutative monoid since ∀x,y. x.plus(y) = y.plus(x).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment