🧠 The Search-and-Select Pattern

🧠 The Search-and-Select Pattern

🔍 Multivalued Functions as Possibility

A function that returns iterables instead of a single value can be understood in two powerful ways:

  • Uncertainty:
    The function doesn’t know the answer yet, so it returns several plausible options.

  • Exploration:
    When input and output share the same type, the function becomes a map: from one point, it shows all the directions you can go next.

That second interpretation unlocks something fundamental:

A multivalued function is a search space.


🧭 Step by Step Toward a Solution

Think about how you actually solve hard problems.

You don’t jump straight to the solution. You:

  • make a rough guess,
  • explore variations,
  • discard bad paths,
  • and gradually converge on something that works.

The Search class models exactly this process.

Each step looks like:

  1. Pull the most promising candidate from a queue
  2. Push all neighboring possibilities back into the queue
  3. Prune the least promising candidates if the queue grows too large

You’re constantly searching and selecting at the same time.

That’s the essence of the Search-and-Select Pattern.


🧰 The Minimal Toolkit

To express this pattern, you only need three concepts:

  1. A queue — to prioritize candidates
  2. A space — to describe how candidates expand
  3. A search — to iterate, filter, and transform results

With just these, you can:

  • Define enormous (or infinite) solution spaces without generating them
  • Stop as soon as a valid solution appears
  • Refine, restrict, or reshape the search declaratively

If this feels familiar, it should.

  • @fizzwiz/fluent provides the fluent vocabulary (Each, What)
  • @fizzwiz/sorted supplies flexible queue strategies
  • @fizzwiz/search ties everything together with Search

const
  search = new Search()
    .from(start)
      .through(space)
        .via(queue),

  result = search
    .which(predicate)
      .sthen(map)
        .what();

What’s happening here:

  • .from(start) — seeds the search
  • .through(space) — defines how candidates expand
  • .via(queue) — controls how candidates are prioritized
  • .which(predicate) — filters unwanted candidates
  • .then(map) — transforms survivors
  • .what() — resolves the search and returns the first valid result

 Clean. Composable. Easy to reason about.


🎯 Why This Pattern Matters

When code starts to feel tangled or fragile, it’s often because it’s secretly a search problem — but expressed imperatively.

Once you recognize the pattern, complexity collapses.

What remains is a small set of expressive operations that:

  • mirror human reasoning,
  • separate intent from mechanics,
  • and scale naturally from simple cases to distributed systems.

 @fizzwiz 


Comments