"""
Exercise 2: lazy pipeline with a generator expression

Implement first_match(iterable, predicate) that returns the FIRST
element of `iterable` for which predicate(element) is True, or None if
no element matches.

Constraints:
  - You MUST build a generator expression that filters by predicate,
    then retrieve just the first value from it (hint: next() with a
    default, or next(iter(...))).
  - The function must NOT scan the entire iterable when a match is
    found early — this matters for infinite/very large iterables, so
    do not convert to a list first.

To prove laziness, the self-check feeds an infinite generator; a
solution that tries to build a list from it first will hang forever.
"""

def first_match(iterable, predicate):
    # TODO: implement using a generator expression + next().
    pass


def _naturals():
    n = 0
    while True:
        yield n
        n += 1


# --- self-check: do not modify below this line ---
r1 = first_match([1, 3, 4, 5, 8], lambda x: x % 2 == 0)
r2 = first_match([1, 3, 5], lambda x: x % 2 == 0)
r3 = first_match(_naturals(), lambda x: x > 1000)  # would hang if not lazy

checks = [
    (r1, 4),
    (r2, None),
    (r3, 1001),
]

all_passed = all(actual == expected for actual, expected in checks)
for actual, expected in checks:
    print(f"got {actual!r}, expected {expected!r} -> {'PASS' if actual == expected else 'FAIL'}")

print("ALL PASS" if all_passed else "SOME FAILED")
