Skip to content
Advertisement

how to keep track of mulitple closure variable in Java lambdas (or JVM language)?

Using Java, I am trying to find a clean way to accumulate multiple different value in a series of lambda. For a concrete example see this following piece of JS (typescript) code:

// some filtering helpers. not really interested in that because there are similar things in Java
const mapFilter = <T,U>(arr: T[], transform: (item: T, idx: number, arr: T[]) => U) => arr.map(transform).filter(Boolean)
const mapFilterFlat = <T,U>(arr: T[], transform: (item: T, idx: number, arr: T[]) => U[]) => mapFilter(arr, transform).flat()

const findDeep = () =>
    mapFilterFlat(someObj.level1Items, A =>
      mapFilterFlat(A.level2Items, B =>
        mapFilter(B.level3Items, C =>
          // I am able to access closure variables so i can push them all in my result, instead of just the last level
          C == something ? ({A, B, C}) : null
        )))

let found: {A: any, B: any, C: any}[] = findDeep();

I am not sure if there are existing Java Stream APIs for accumulate such a result. Maybe it’s not really possible and i should look into another JVM language ?

I eventually did this, but it’s not really concise (although i know Java is not really):

 public class Finder implements BiFunction<SomeObj, Predicate<SomeObj>, List<State>> {
    static class State {
        Integer A;
        String B;
        List C;
        
        static State from(Map<String, Object> inputs) {
            var res = new State();
            res.A = (Integer) inputs.get("A");
            res.B = (String) inputs.get("B");
            res.C = (List) inputs.get("C");
            return res;
        }
    }
    
    Map<String, Object> fields;

    <T> T store(String key, T value) {
        return (T) fields.put(key, value);
    }
    
    public List<State> apply(SomeObj someObj, Predicate<C> predicate) {
        fields = new HashMap<>();
        return config.level1Items
              .stream()
              .flatMap(A -> store("A", A).level2Items.stream())
              .flatMap(B -> store("B", B).level3Items.stream())
              .peek(C -> store("C", C))
              .filter(predicate)
              .map(o -> State.from(fields))
              .collect(Collectors.toList());
    }
}

I am not even sure that the BiFunction implementation is useful. Thanks for your guidances

Answer

You are translating TypeScript but you are no translating as it was: you are not “inheriting” the depth of lambda, there are all at the same level and they all don’t see the variable from their parent context.

const findDeep = () =>
    mapFilterFlat(someObj.level1Items, A =>
      mapFilterFlat(A.level2Items, B =>
        mapFilter(B.level3Items, C =>
          // I am able to access closure variables so i can push them all in my result, instead of just the last level
          C == something ? ({A, B, C}) : null
        )))

This is not the same as:

    return config.level1Items
          .stream()
          .flatMap(A -> store("A", A).level2Items.stream())
          .flatMap(B -> store("B", B).level3Items.stream())
          .peek(C -> store("C", C))
          .filter(predicate)
          .map(o -> State.from(fields))
          .collect(Collectors.toList());

This should be something like this:

    return config.level1Items
          .stream()
          .flatMap(A -> 
             store("A", A).level2Items
                          .stream()
                          .flatMap(B -> store("B", B).level3Items
                                                     .stream())
           )
          .peek(C -> store("C", C)) // the same must be done here
          .filter(predicate)
          .map(o -> State.from(fields))
          .collect(Collectors.toList());

If I try to understand your algorithm, you are trying to get all permutation of {A, B, C} where C = something: your code should be something like this, using forEach to iterate over items of Collection/Iterator.

List<Triple<A,B,C>>> collector = new ArrayList<>();
config.level1Items.forEach(a -> {
  a.level2Items.forEach(b -> {
    b.level3Items.forEach(c -> {
      if (c.equals(something)) {
        collector.add(new Triple<>(a, b, c));
      }
    }
  });
});

You don’t need a stream for that.

Triple is simply an implementation of tuple of 3 value, for example the one at commons-lang3.

Advertisement