4 ms·
One thing I'm not sure is possible in this proposal, is there a way to make the interpolation "lazy", such that the string (and the evaluation of its interpolat
by ninkendo 4y ago
One thing I'm not sure is possible in this proposal, is there a way to make the interpolation "lazy", such that the string (and the evaluation of its interpolated components) can be skipped if the string isn't ultimately used?
In swift, there's some nice quasi-laziness you can add to function parameters, so that (say) a logging function that can fully skip evaluating a string sent to it with the `@autoclosure` syntax:
func log(message: @autoclosure () -> String, level: Level = .info) {
guard level >= configuredLevel else {
return
}
actuallyLog(message())
}
And call it like:
log(message: "User is \(someExpensiveFunction(user))", level: .debug)
And if the configured log level does not include debug messages, `someExpensiveFunction(user)` doesn't get called.
This works because @autoclosure lets you take a parameter that is "function returning String", but callers can just pretend they're passing it a String, without having to decorate it in a function. The compiled code will turn it into a closure behind the scenes, and thus it'll be evaluated lazily.
Not sure if there's any way to do something like this in Java with this proposal...
- lazulicurio 4y agoIt's a tad more verbose, but I assume the simplest way would just be to use a standard closure. void log(Supplier<? extends String> messageSupplier); log(() -> log.info"User is \{someExpensiveFunction(user)}"); The pattern of using Supplier to provide a lazy argument is pretty well-established afaict.
- layer8 4y agoYou probably would pass the equivalent of new Object() { public String toString() { return <actual expression here> ; } } You can wrap that into a helper function that takes a lambda, so you can write STR."\{lazy(() -> <actual expression here>)}"
- vips7L 4y agoSupplier<T>
- layer8 4y agoThat would require explicit support by each template processor implementation. The Object#toString approach works independently of the template processor (assuming they all call toString() on the parameters). Since the string template interface is untyped (parameters are arbitrary Objects), having case distinctions based on the type parameter is fiddly. What if you pass an instance of a class that has a genuine toString() but also happens to implement Supplier<Foo> for some reason? Or if you pass a Supplier<Supplier<Foo>>, will it recurse? What if you pass a Callable or a Future? Will those work as well? This only creates the opportunity for opaque “magic” behavior.
- vips7L 4y agoI think you're thinking about it too much. log.info(() -> STR."template \{here}");
- layer8 4y agoThe question was whether string templates will support laziness, not whether whatever the string template is eventually used for supports laziness. String templates supporting laziness would make you independent from what the point-of-use supports.
- vips7L 4y agoI think that's the wrong question. String templates don't need to support laziness. It's a well established pattern in Java that laziness is done via Supplier's and it directly solves op's question about logging where the level isn't enabled. This is exactly how logback and other logging implementations handle it now.