Skip to content

pick (C003) / eager: a first-match search that binds its recursion above the pick never stops early #199

Description

@ngngardner

Found while profiling bolt lint (branch autoresearch/bolt-lint-perf).

Shape

pick tells you to "bind the call once above the pick (+more = go(rest))". That's right for a filter, where both branches use more. For a first-match search, though, the then-branch never needs more, and binding it just moves the full walk out of the rule's sight:

def deps_of(es: List<&2, Edge>, +pp: String) -> List<&2, String>:
  match es:
    case Con{Edge{+ep, ds}, rest}:
      +more = deps_of(rest, pp)
      Bool.pick(List<&2, String>, String.eq(ep, pp), ds, more)   # the hit at the head still walks to the end

The same shape appears in Bind.find (the environment search) and Laws.res.all-style searches. pick's own description names this bug ("a search never stops early"), but it only catches the unbound form.

Cost here

Making deps_of stop early (Lazy.stop(.., hit, ds, _u => deps_of(rest, pp))) took coverage over bolt's tree from 10.2 s to 9.4 s on its own. It was one of the lookups in the closure-walk fix (10.2 s to 2.75 s overall). Honestly the gain depends on the data: the same change to Bind.find measured nothing, because environments are short.

Proposal

Report +x = self(..) where x is read only in the else branch of a Bool.pick that follows it (the then-branch doesn't mention x). Suggest Lazy.stop(T, hit, value, _u => self(..)). A filter (d <> more, more) uses x in both branches, so it's never reported.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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