Question about LazyArgs

23 views
Skip to first unread message

zhenya leonov

unread,
May 8, 2020, 3:14:31 PM5/8/20
to Flogger Discuss
I don't understand why Flogger is using LazyArgs as opposed to just a Supplier like JUL starting with Java8.

Can someone enlighten me?

Why is this:

logger.atSevere().log("Value: %s", lazy(() -> doExpensiveCalculation()));

better than:

logger.atSevere().log("Value: %s", () -> doExpensiveCalculation());

What am I missing?
Thanks guys.


Louis Wasserman

unread,
May 8, 2020, 3:24:04 PM5/8/20
to zhenya leonov, Flogger Discuss
It can't work for arbitrary numbers of arguments.  Consider:

logger.atSevere().log("%s %s %s %s %s %s", () -> a(), () -> b(), c, () -> d(), () -> e());

To pass a lambda to a function in Java, there must be an unambiguous functional interface type for that lambda to implement.

So to make this work, you'd have to actually have a defined log method that had the particular type (Supplier<?>, Supplier<?>, Object, Supplier<?>, Supplier<?>).  You can't just accept a vararg of Supplier, because the suppliers might be arbitrarily mixed with non-supplier arguments.  You can't just accept a vararg of Object, because then the compiler will type-error, saying it can't figure out what type to implement with the lambda.  So no varargs approach will let you mix both suppliers and other arguments, you have to cap the number of arguments you support, or force the user to use a supplier for every argument under certain specific conditions.

The lazy arg approach scales to an arbitrary number of arguments, it doesn't actually incur any extra runtime overhead, it's consistent (there are no rules that differ depending on how many total arguments you have), and it self-documents what it's doing (evaluating an arg lazily).



--
You received this message because you are subscribed to the Google Groups "Flogger Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to flogger-discu...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/flogger-discuss/cf94937c-1021-4d50-a8b4-b11709a74aee%40googlegroups.com.


--
Louis Wasserman (he/him)

David Beaumont

unread,
May 8, 2020, 3:37:36 PM5/8/20
to zhenya leonov, Flogger Discuss
It's really two things:
1) Flogger's original design predates Java8 (by about 4 years) and lambdas didn't exist then, so this would have been problematic then.
There's also the problem that Flogger's core API is compatible all the way back to Java 6, so adding lambdas would break backwards compatibility.

However, the real issue is that it's basically impossible to use lambdas in the way you describe, since it just doesn't make for a practical API.

Consider the methods that would be needed to support this. Assume that you start with the current Flogger log methods:

log(String, Object)
log(String, Object, Object)
log(String, Object, Object, Object)
log(String, Object, Object, Object, Object, ...)
So now you need to ask "what is a lambda?", "what type is it?" and "what should the methods look like to accommodate this?"

Firstly, a lambda is not a real thing, it's compiler smoke & mirrors. The "type" of a lambda is whatever the receiver expects to which it can be coerced.
So "log("Hello %s", () -> expensive())" can only compile if the log method looks like "log(String msg, Supplier<?> lazyArg)".

But what about two arguments:
  log("Hello %s, World %s", () -> expensive(), cheap)

That only compiles if you have:
  log(String, Supplier<?>, Object)
but now you also need to handle other combinations like:
  log(String, Object, Supplier<?> lazyArg)
  log(String, Supplier<?>, Supplier<?>)

For 3 arguments, you need 8 methods for all combinations and so on. Since Flogger avoids varargs up to 10 parameters, you'd need > 1000 new methods.
Even worse, once you get an arbitrary number of methods using varargs, it stops working completely and stops compiling.

And finally, even if you got all of that working, what if I wanted to log something that just happens to be a "Supplier" ? I don't want it's value to be logged, I want the Supplier itself to be logged.
No API can tell the difference here so will get it wrong in at least some cases.

Note that the JDK logger *doesn't* do what you describe, it wants a lambda for the entire message, not a message plus separate lazy args.
This is problematic because it encourages logging things serialized as strings, and Flogger supports structured logging (only the backend cares about formatting), so this approach would go against that aim.

But ... having said all that, Flogger does support lambdas, and it supports them for free via "lazy()"
Look at the implementation of the "lazy()" method. It's completely *empty*.

All "lazy()" does is present a new type (one that's compatible with Supplier, but not Supplier itself) so that a given lamdba can be "coerced" into that type.
Now, with the known special type in hand, Flogger can use "instanceof" to figure out which arguments are lazy without any ambiguity.

So, as you can see, it's really not feasible to do what you're suggesting (sadly) and something like "lazy()" is a very "cheap" way to solve the problem while retaining structured parameters.

Hope this help explain things,
    David


--
You received this message because you are subscribed to the Google Groups "Flogger Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to flogger-discu...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/flogger-discuss/cf94937c-1021-4d50-a8b4-b11709a74aee%40googlegroups.com.


--
David Beaumont :: Îñţérñåţîöñåļîžåţîờñ Libraries :: Google
Google Switzerland GmbH., Brandschenkestrasse 110, CH-8002, Zürich - Switzerland

zhenya

unread,
May 8, 2020, 5:32:45 PM5/8/20
to David Beaumont, Flogger Discuss
David, Louis, thanks for the detail explanation.
I did look at LazyArgs in detail. I was just not considering what use cases it solved.

I should have been a little more clear in my question. When I use JUL with an expensive operation, I use () -> String.format("%s, %s, %s, expensiveArg1(), cheapArg2, cheapArg3()). At that point the cost of creating an array is trivial. So that solves the problem with multiple arguments.

But, backwards compatibility aside, I see how the Flogger API is better.
I didn't consider ...because it encourages logging things serialized as strings, and Flogger supports structured logging or if I wanted to log something that just happens to be a "Supplier", among other issues mentioned by David.

David Beaumont

unread,
May 8, 2020, 5:40:24 PM5/8/20
to zhenya, Flogger Discuss
Thanks for the feedback.

I might tweak docs to explain this a bit more, since you're definitely not the first person to wonder about this :)

Cheers,
    David
Reply all
Reply to author
Forward
0 new messages