Open wadoon opened 2 years ago
I think it is a really interesting topic, but I wonder if it can really be covered in a github issue ;-P
Carsten has written his thesis on how to deal with that in KeY using a variety of options, and David has an experience report on dealing with lambdas in practice.
I think it is a really interesting topic, but I wonder if it can really be covered in a github issue ;-P
The alternative would be a call or a JML workshop :)
I hope, we can at least collect the discussion points, and then proceed the substantial discussion.
This is a significant topic. It is not sufficient just to have syntactical/language design ideas. They need to be worked out in the context of actual verification problems and the common usage of lambda expressions.
There is another alternative to the syntax Remember that in Java all lambda functions are instances of a functional interface. One cannot have a stand-alone lambda expression. It is always in a context in which its type can be inferred. That means we can use specification-level type casting to give a type with specifications.
For example, Java has a Function<T,U>, whose specification is so general it cannot be used to prove anything. But we can write
@FunctionalInterface
interface PurePredicate<T> extends Predicate<T> {
//@ also public normal_behavior
//@ assigns \nothing;
boolean test(T t);
}
and then in actual use
public static boolean all(List<Integer> list, Predicate<Integer> predicate) {
return list.stream().allMatch(i -> (/*@ (PurePredicate<Integer>)*/predicate).test(i));
}
or even just
public static boolean all(List<Integer> list, Predicate<Integer> predicate) {
return list.stream().allMatch(/*@ (PurePredicate<Integer>)*/predicate);
}
Of course, there is a proof obligation that the predicate being cast actually conforms to the more restricted specification. So one might want some syntax for the signature as well:
public static boolean all(List<Integer> list, /*@{PurePredicate<Integer>}*/ Predicate<Integer> predicate) {
return list.stream().allMatch(i -> predicate.test(i));
}
to indicate that, for specification purposes, the argument has a more restricted type -- and then when all
is called, say with all(list, k->k>0)
, one must prove that the given lambda function satisfies the spec of PurePredicate, not just Predicate.
If you have control fo the source code, you can just use PurePredicate where you like. But the library methods for functions and streams use classes with very general specs that cannot be reasoned with.
I find that naming a specification with an interface declaration a) is more readable -- name shows intent and oe can see where the same spec is reused b) allows reuse of the specification, simplifying proof obligations c) fits existing Java syntax more closely with the one downside of d) the actual specs are in a place different than where used (in the same way that a called methods specs are placed with the method declaration rather than its point of use)
I agree that this is a research topic that deserves further treatment.
Currently, jmlparser misses support of lambda expression. I think we should start to settle definitions for lambda expressions and higher order functions. I see the following the discussion points:
How to specify a lambda expression? For example:
The inline contract has to be sub-contract of the every contract at the SAM in the interface.
How to use higher order functions (lambda expression or method references) inside JML constructs? Beside the use inside contracts or invariants, we also have the ghost statements which could enclose local variables.
How to specify requirements on a given higher order function? Image you have a function like:
How can I specify that the given
Supplier
also hasassignable \nothing;
clause?Do I missed a discussion point?
Support for lambda expression in KeY were explored by @mattulbrich and @csicar.