Skip to content

Add eval for predicate loops as anyMatch #49

Description

@martinfrancois

Problem

The Streams skill should reliably suggest anyMatch(...) when a loop only answers whether any element satisfies a predicate, and should avoid this suggestion when the loop performs side effects, builds diagnostics, or depends on index-specific output.

This is eval-worthy because a codebase-wide loop audit found pure predicate loops mixed with many loops that should remain imperative. The skill should distinguish the simple predicate case from side-effecting or stateful loops.

Code before the prompt was executed

The pre-refactor code used loops to return or record whether a predicate matched.

private static boolean hasNonObjectBoardRow(JsonNode root) {
    JsonNode boards = root.path("boards");
    for (JsonNode board : boards) {
        if (!board.isObject()) {
            return true;
        }
    }
    return false;
}

A second case added one warning if any invalid value was present:

private static void requireWritableRoots(JsonNode board, String label, List<String> warnings) {
    JsonNode roots = board.get("additionalWritableRoots");
    if (roots == null) {
        return;
    }
    if (!roots.isArray()) {
        warnings.add("Connected-board manifest entry " + label
                + " field additionalWritableRoots must be an array of path strings.");
        return;
    }
    for (JsonNode root : roots) {
        if (!root.isTextual() || root.asText().isBlank()) {
            warnings.add("Connected-board manifest entry " + label
                    + " field additionalWritableRoots must contain only non-blank path strings.");
            return;
        }
    }
}

Prompt that caused the implementation

The maintainer asked to inspect every for loop, pass the containing method through the Streams skill, and refactor only when the stream version improves the code. The prompt explicitly allowed broader suggestions involving callers or callees when they make the code better.

Later prompt that exposed the issue

This was found during the stream audit itself, not by a later correction.

Prompt-produced code before maintainer correction

The reviewed code was the pre-prompt loop code above. There was no rejected intermediate implementation.

Why the prompt-produced code is weak

The loops are correct, but they encode a simple existence question manually. The early return is exactly the short-circuiting behavior anyMatch(...) already communicates. Leaving the loop makes the reader inspect control flow to learn that no per-element side effect is intended.

Behavior-equivalence analysis

The refactor is behavior-preserving because:

  • encounter order does not select a returned element;
  • the result is only a boolean;
  • anyMatch(...) short-circuits like the loop;
  • the warning case still adds exactly one warning after the predicate result is known;
  • no IO, mutation per element, checked exception, or index-specific message is hidden inside the stream.

The skill should not apply this pattern to loops that write rows, call APIs, clean up processes, probe ports, scan characters, or build index-specific diagnostics.

Maintainer-preferred code

private static boolean hasNonObjectBoardRow(JsonNode root) {
    JsonNode boards = root.path("boards");
    return StreamSupport.stream(boards.spliterator(), false)
            .anyMatch(board -> !board.isObject());
}

For the warning case:

boolean hasInvalidRoot = StreamSupport.stream(roots.spliterator(), false)
        .anyMatch(root -> !root.isTextual() || root.asText().isBlank());
if (hasInvalidRoot) {
    warnings.add("Connected-board manifest entry " + label
            + " field additionalWritableRoots must contain only non-blank path strings.");
}

Why the replacement is better

The stream terminal names the operation: the method asks whether any item matches the invalid condition. The short-circuit remains explicit, and the warning write stays outside the pipeline so the stream is still result-producing rather than side-effecting.

Desired eval behavior

  • Reward replacing pure boolean predicate loops with anyMatch(...).
  • Reward keeping the terminal side effect, such as adding one warning, outside the stream.
  • Reward recognizing Jackson JsonNode iterables via StreamSupport.stream(..., false) when no collection stream is available.
  • Reward rejecting this refactor for loops whose body performs IO, writes rows, mutates per-element output, depends on an index, or has complex early-exit state.
  • Reward explaining why findAny() is not the right API when the result needed is a boolean.

Anti-patterns the eval should reject

  • Using .forEach(...) with an external boolean holder.
  • Collecting filtered values and then checking whether the collection is empty.
  • Moving warning writes or other side effects inside the stream pipeline.
  • Rewriting index-sensitive diagnostics to streams without preserving the reported index.

Suggested eval name

predicate-loop-any-match

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions