20

Is there a function which accepts a reference to a lambda expression and returns a boolean saying whether the lambda expression is stateless or not? How can the statefulness of a lambda expression be determined?

Boann
  • 48,794
  • 16
  • 117
  • 146
  • 6
    you will get, *why you need this*, or *this is an XY problem*, but otherwise I love the question, I can't think of an answer though. – Eugene Feb 20 '18 at 14:44
  • 3
    Are you able to provide us with some examples of stateless and stateful lambda expressions? – Jacob G. Feb 20 '18 at 14:44
  • It's just my interest in Java, not XY problem or something else. @Eugene – Soner from The Ottoman Empire Feb 20 '18 at 14:50
  • @snr I *know*, you will get from other people, that was my point – Eugene Feb 20 '18 at 14:51
  • 3
    I don't see any way to determine in a generic way without introspection/byte-code inspection, since "stateful" can get pretty complicated, e.g., writing to the console wouldn't be stateless any more. – Dave Newton Feb 20 '18 at 15:30
  • @DaveNewton Writing to the console is not a stateful operation, but a *side-effect*. I mean, I support your claim, in the sense that referencing objects declared outside of the body of the lambda don't immediately mean that the lambda is stateful. – fps Feb 20 '18 at 18:44
  • @FedericoPeraltaSchaffner I'd probably disagree on a technicality because it changes the state of the console or whatever stream it's set to. – Dave Newton Feb 20 '18 at 18:49
  • @DaveNewton Yes, seen from that perspective, I agree. If you print to the console, you are changing its state so that it now displays the text. In fact, I think that every side-effect can be seen as a stateful operation. – fps Feb 20 '18 at 19:15
  • @FedericoPeraltaSchaffner and to be honest if this would have been possible at all, it should have been put into the `javac`l to at least issue a warning when `peek/filter/map` are stateful... – Eugene Feb 20 '18 at 19:50
  • 1
    @Eugene perhaps in Java 12… – Holger Feb 21 '18 at 08:52
  • @Holger I sense a bit of irony here... or real stuff (confused) :| – Eugene Feb 21 '18 at 08:53
  • 2
    @Eugene well, I didn’t say Java 20. In other words, I see technical possibilities, but don’t know of any actual plan for the next versions for which plans exist. But given Oracles current plans of short release cycles, version 12 would be rather optimistic. – Holger Feb 21 '18 at 08:58
  • @Holger ok thank you; so I take it as this being possible? Really? I could not even up-vote the top rated answer here, since it sort is a hack that would not even work sometimes and it not a proof that a lambda is stateful, just a guess. So is it really possible? – Eugene Feb 21 '18 at 09:02
  • 2
    @Eugene well, for a compiler knowing the code, that would be possible, see how C++ handles `const` objects, where you can only call methods that don’t modify the object. And you’re right, this has nothing to do with whether the lambda expression captures values. – Holger Feb 21 '18 at 09:07

4 Answers4

12

Well, a lambda expression is just an instance of a special anonymous class that only has one method. Anonymous classes can "capture" variables that are in the surrounding scope. If your definition of a stateful class is one that carries mutable stuff in its fields (otherwise it's pretty much just a constant), then you're in luck, because that's how capture seems to be implemented. Here is a little experiment :

import java.lang.reflect.Field;
import java.util.function.Function;

public class Test {
    public static void main(String[] args) {
        final StringBuilder captured = new StringBuilder("foo");
        final String inlined = "bar";
        Function<String, String> lambda = x -> {
            captured.append(x);
            captured.append(inlined);

            return captured.toString();
        };

        for (Field field : lambda.getClass().getDeclaredFields())
            System.out.println(field);
    }
}

The output looks something like this :

private final java.lang.StringBuilder Test$$Lambda$1/424058530.arg$1

The StringBuilder reference got turned into a field of the anonymous lambda class (and the final String inlined constant was inlined for efficiency, but that's beside the point). So this function should do in most cases :

public static boolean hasState(Function<?,?> lambda) {
    return lambda.getClass().getDeclaredFields().length > 0;
}

EDIT : as pointed out by @Federico this is implementation-specific behavior and might not work on some exotic environments or future versions of the Oracle / OpenJDK JVM.

Prags
  • 2,457
  • 2
  • 21
  • 38
ttzn
  • 2,543
  • 22
  • 26
  • 6
    I'm sorry, but I don't believe that having or not having fields is a sufficient condition in making a method implementation (a lambda) stateless nor statefull. I'm pretty sure `() -> ThreadLocalRandom.current().nextInt()` cannot be considered 'stateless' but your test won't show any captured fields. – Jeremy Grand Feb 20 '18 at 15:27
  • 1
    @JeremyGrand that's exact, but I have made my understanding of "stateful" pretty clear by restricting it to local data. Global side-effects are out of the scope of my answer and would require thorough (and most likely hopeless) interpretation of every single line of code. – ttzn Feb 20 '18 at 15:34
  • By the way, I don't think it's fair to consider the PRNG in `ThreadLocalRandom` as belonging to the lambda's state, because by that definition every single entity in a Java program shares the same state (kinda true, but not very useful point of view IMHO). – ttzn Feb 20 '18 at 15:41
  • @CoffeeNinja Yes `ThreadLocalRandom` is not the best example, but any storage of state in a `static` variable will be invisible by the field count; eg `System.setProperty`. – daniu Feb 20 '18 at 16:03
  • @daniu honestly I can't see any difference, my point stands for any `static` manipulation. – ttzn Feb 20 '18 at 16:16
  • 5
    If you define the lambda in a non-static context, you also get a reference to the enclosing `this` instance... All this is carefully and deliberately undocumented, and cannot be trusted in any way. It is explicitly said that all this stuff is *implementation-specific*, meaning that no useful code can be written that uses anything of this. – fps Feb 20 '18 at 18:33
  • 1
    You are right about the implementation-specific nature of this. I will amend to make it clear it cannot be trusted to work on all environments. – ttzn Feb 20 '18 at 18:46
  • 2
    @FedericoPeraltaSchaffner you don’t “get” a reference to the enclosing `this` instance. A lambda expression may capture the enclosing `this` instance, if it uses it, either explicitly or by using its instance fields. A lambda expression which does not use it, does not capture `this`. This has been acknowledged by the language designers, however, they forgot to state it explicitly in the specification. – Holger Feb 21 '18 at 08:31
  • 3
    @CoffeeNinja there seem to be some confusion between what a lambda expression *could* use and what it *does* use. Since your code only evaluates what the instance *does* use, it’s fair to mention that it does not notice whether the lambda expression does use things like `ThreadLocalRandom` or `static` variables. And by the way, it doesn’t detect whether it “carries mutable stuff in its fields”, as all these fields are `final` and you would have to evaluate the referenced objects, if any, to find out whether they are mutable. – Holger Feb 21 '18 at 08:34
3

Here's a simple and stupid idea. Just check if your lambda has fields.

For instance, consider the following stateful lambda.

  List<Integer> serialStorage = new ArrayList<>();
  Function<? super Integer, ? extends Integer> statefulLambda =
      e -> { serialStorage.add(e); return e; };

This statefulLambda has a private final internal field arg$1 which obviously references serialStorage. So

  statefulLambda.getClass().getDeclaredFields().length > 0

could be used as indicator that lambda is stateful.

However I have no idea if this will generally work.

lexicore
  • 42,748
  • 17
  • 132
  • 221
  • This probably works in most cases (i have tested it with an online compiler), but it has false positives with array access, and true negatives with static field access – Ferrybig Feb 20 '18 at 21:20
3

No, it is not generally possible. The suggested approach of checking whether the lambda belongs to a class with a field is the next best thing, but having a field does not equal having a state.

class Stateless {
    int result = 0;
    public int getResult() { return result; }
}

It is possible to prove statefulness by finding two input sequence for which a given input combination returns a different result. However, it is not possible to prove that such a input sequence does not exist (any input sequence might produce a different result if prepended by another invocation).

(Even if you check the values of fields found via reflection, those might change without influencing the lambda's result, therefore not really making it stateful).

Here's a short compilable example showing both false positive and negatives, disproving the notion:

public class StatefulLambda {
    static AtomicInteger counter = new AtomicInteger();

    public static void main(String[] args) {
        // false negative: will return different result each call
        System.out.println(hasState(i -> counter.incrementAndGet()));

        // false positive: will always return the same result
        Object object = new Object() {
            final int i = 0;
        };
        System.out.println(hasState(i -> object.toString()));
    }

    private static boolean hasState(Function<?,?> lambda) {
        return lambda.getClass().getDeclaredFields().length > 0;
    }
}
daniu
  • 14,137
  • 4
  • 32
  • 53
  • You are right: lambda can be stateless with fields and statful without fields. But it seems like it's the best we can do. – lexicore Feb 20 '18 at 15:54
1

I would argue that it is not possible to write a function that can determine if a lambda is stateless or not:

Looking for example at the filter method of the Stream API, the javadoc states that the parameter must be "a [...] stateless predicate" and also links to the API's definition of stateless.

If there was a way to determine if the parameter of the filter (or any other) method was stateless or not, the Stream class would have included the possibility to throw an IllegalArgumentException in case the parameter was a stateful lambda. As this has not been implemented and only a warning was added to the javadocs, one can conclude that there is no way write a function that can determine if a lambda lambda is stateless.


Edit (after reading the comments from Eric): There are plenty of situations where an implementation team makes implementation choices to not implement a particular feature; we usually cannot conclude from those choices that the feature is impossible for someone else to implement. In this special case, I believe its implausible that the Java 8 designers would not have found or done it if there was a (computational cheap) solution.
werner
  • 13,518
  • 6
  • 30
  • 45
  • 2
    I completely disagree with this answer. Consider a related problem: a sort algorithm takes a comparator; the comparator is required to impose a total order. There is no exception if a non-totally-ordering comparator is passed in, but it is *possible* to check to see if a total order is imposed! You simply call the comparator with every possible pair of items in the set being sorted, record the results in a table, and throw for any pair that violates asymmetry, reflexivity or transitivity. Real implementations do not do this even though it is possible. – Eric Lippert Feb 20 '18 at 23:04
  • 1
    We cannot reason that "because the implementation team chose to not implement feature X, then X must be impossible". Implementation teams make all kinds of decisions for all kinds of reasons. – Eric Lippert Feb 20 '18 at 23:04
  • I disagree with both counter arguments. The comparator argument holds true for a comparator on Booleans; but for larger data types, the described algorithm would not work. For a 8 byte Long type you would have to compare 3.4e38 pairs, which is not achievable in a reasonable amount of time. Real implementations do not do this kind of checks because it is practical impossible. – werner Feb 21 '18 at 21:10
  • The second argument would hold true if it was about the kind of code that I write every day. But the Lambda Api has been one of the major changes in Java 8, the specification and community discussions lasted several years. It would have been considered as a major design flaw if a possibility to detect the statefulness of a lambda existed and it would not have been exploited. I trust that the Java community would not have accepted such a flaw. Of course, my reasoning is not - and never was - a strict proof in a rigorous mathematical sense. – werner Feb 21 '18 at 21:10
  • 1
    The described algorithm *does* work and has been practically implemented. Do you see how? (Hint: your assumption that the entire space of possible inputs to the comparator must be validated is wrong. What is the actual space of inputs that must be validated?) Regardless of my specific example though: there are *plenty* of situations where an implementation team makes implementation choices to not implement a particular feature; we cannot conclude from those choices that the feature is impossible for someone else to implement. – Eric Lippert Feb 21 '18 at 21:56
  • Maybe a bit off-topic, but what about this comparator: `public static class BadComparator implements Comparator {public int compare(Long o1, Long o2) {return o1.equals(1l) && o2.equals(2l) ? o2.compareTo(o1) : o1.compareTo(o2);}}` ? To detect this kind of bug, of you have to scan the entire space to be sure not not miss something. Only if you take reflexivity and/or transitivity for granted before the test, there are shorter ways. – werner Feb 23 '18 at 14:24
  • @werner: Eric Lippert’s comments were about verifying that the comparator is consistent for the particular collection you’re going to sort. That would be enough to prove that the particular sort operation is not subject to inconsistency. For checking whether a lambda expression is stateless, it’s even simpler. Don’t try to predict conditionals for particular input values; just assume that every branch could be taken and check that every branch is stateless. Writing unreachable code paths would be useless anyway. Compare to how the compiler enforces that local variables are always initialized. – Holger Mar 01 '18 at 10:22