"Given two streams, form a new stream by applying a binary
 [[mapping function|collecting]] to pairs of elements in the 
 given streams.  If one of the streams is longer than the 
 other, simply ignore additional elements of the longer 
 stream with no pair in the other stream. The length of the 
 resulting stream is the length of the shorter of the two 
 given streams.
 
 For any given streams `xs` and `ys`, and mapping function 
 `f`, `mapPairs()` may be defined in terms of 
 [[Iterable.map]], [[zipPairs]], and [[unflatten]]:
 
     mapPairs(f, xs, ys) == zipPairs(xs, ys).map(unflatten(f))
 
 For example the expression
 
     mapPairs((Float x, Float y) => (x^2+y^2)^0.5, 
             {3.0, 5.0, 6.0, 9.0}, {4.0, 12.0, 8.0, 12.0})
     
 evaluates to the stream `{ 5.0, 13.0, 10.0, 15.0 }`."
tagged("Streams")
shared Iterable<Result,FirstAbsent|SecondAbsent> 
mapPairs<Result,First,Second,FirstAbsent,SecondAbsent>(
    "The mapping function to apply to the pair of elements."
    Result collecting(First first, Second second),
    Iterable<First,FirstAbsent> firstIterable,
    Iterable<Second,SecondAbsent> secondIterable)
        given FirstAbsent satisfies Null
        given SecondAbsent satisfies Null {
    object iterable 
            satisfies Iterable<Result,FirstAbsent|SecondAbsent> {
        shared actual Iterator<Result> iterator() {
            object iterator 
                    satisfies Iterator<Result> {
                value firstIter = firstIterable.iterator();
                value secondIter = secondIterable.iterator();
                shared actual Result|Finished next() {
                    if (!is Finished first = firstIter.next(),
                        !is Finished second = secondIter.next()) {
                        return collecting(first,second);
                    }
                    else {
                        return finished;
                    }
                }
                string => "``outer.string``.iterator()";
            }
            return iterator;
        }
    }
    return iterable;
}

"Given two streams, return the first pair of elements in the 
 given streams that satisfies the given binary [[predicate
 function|selecting]], or null if no pair of elements 
 satisfies the predicate. If one of the streams is longer 
 than the other, simply ignore additional elements of the 
 longer stream with no pair in the other stream.
 
 For any given streams `xs` and `ys`, and predicate function 
 `p`, `findPair()` may be defined in terms of 
 [[Iterable.find]], [[zipPairs]], and [[unflatten]]:
 
     findPair(p, xs, ys) == zipPairs(xs, ys).find(unflatten(p))"
tagged("Streams")
since("1.1.0")
shared [First,Second]? findPair<First,Second>(
    "The binary predicate function to apply to each pair of 
     elements."
    Boolean selecting(First first, Second second),
    {First*} firstIterable, {Second*} secondIterable) {
    value firstIter = firstIterable.iterator();
    value secondIter = secondIterable.iterator();
    while (!is Finished first = firstIter.next(),
           !is Finished second = secondIter.next()) {
        if (selecting(first,second)) {
            return [first,second];
        }
    }
    return null;
}

"Given two streams, return `true` if every pair of elements 
 in the given streams satisfies the given binary [[predicate
 function|selecting]], or `false` otherwise. If one of the
 streams is longer than the other, simply ignore additional 
 elements of the longer stream with no pair in the other 
 stream. If either stream is empty, return `true`.
 
 For any given streams `xs` and `ys`, and predicate function 
 `p`, `everyPair()` may be defined in terms of 
 [[Iterable.every]], [[zipPairs]], and [[unflatten]]:
 
     everyPair(p, xs, ys) == zipPairs(xs, ys).every(unflatten(p))"
see (`function corresponding`,
     `function anyPair`)
tagged("Streams")
since("1.1.0")
shared Boolean everyPair<First,Second>(
    "The binary predicate function to apply to each pair of 
     elements."
    Boolean selecting(First first, Second second),
    {First*} firstIterable, {Second*} secondIterable) {
    value firstIter = firstIterable.iterator();
    value secondIter = secondIterable.iterator();
    while (!is Finished first = firstIter.next(),
           !is Finished second = secondIter.next()) {
        if (!selecting(first,second)) {
            return false;
        }
    }
    return true;
}

"Given two streams, return `true` if some pair of elements 
 in the given streams satisfies the given binary [[predicate
 function|selecting]], or `false` otherwise. If one of the
 streams is longer than the other, simply ignore additional 
 elements of the longer stream with no pair in the other 
 stream. If either stream is empty, return `false`.
 
 For any given streams `xs` and `ys`, and predicate function 
 `p`, `anyPair()` may be defined in terms of 
 [[Iterable.any]], [[zipPairs]], and [[unflatten]]:
 
     anyPair(p, xs, ys) == zipPairs(xs, ys).any(unflatten(p))"
see (`function everyPair`)
tagged("Streams")
since("1.1.0")
shared Boolean anyPair<First,Second>(
    "The binary predicate function to apply to each pair of 
     elements."
    Boolean selecting(First first, Second second),
    {First*} firstIterable, {Second*} secondIterable) {
    value firstIter = firstIterable.iterator();
    value secondIter = secondIterable.iterator();
    while (!is Finished first = firstIter.next(),
           !is Finished second = secondIter.next()) {
        if (selecting(first,second)) {
            return true;
        }
    }
    return false;
}

"Given two streams, return the result of applying the given 
 [[accumulating function|accumulating]] to each pair of 
 elements of the given streams in turn. If one of the 
 streams is longer than the other, simply ignore additional 
 elements of the longer stream with no pair in the other 
 stream.
 
 For any given streams `xs` and `ys`, initial value `z`, and
 combining function `f`, `foldPairs()` may be defined in 
 terms of [[Iterable.fold]], [[zipPairs]], and [[unflatten]]:
 
     foldPairs(z, f, xs, ys) == zipPairs(xs, ys).fold(z)(unflatten(f))"
tagged("Streams")
since("1.1.0")
shared Result foldPairs<Result,First,Second>(
    Result initial,
    "The accumulating function to apply to each pair of 
     elements."
    Result accumulating(Result partial, 
                        First first, Second second),
    {First*} firstIterable, {Second*} secondIterable) {
    value firstIter = firstIterable.iterator();
    value secondIter = secondIterable.iterator();
    variable value partial = initial;
    while (!is Finished first = firstIter.next(),
           !is Finished second = secondIter.next()) {
        partial = accumulating(partial, first, second);
    }
    return partial;
}