Skip to content

Fix some Fold/Scan bugs, minor doc issues - #3258

Merged
harendra-kumar merged 8 commits into
masterfrom
fold-scan-fixes
Oct 15, 2026
Merged

harendra-kumar merged 8 commits into
masterfrom
fold-scan-fixes

Conversation

@harendra-kumar

Copy link
Copy Markdown
Member

Mostly small correctness fixes in Fold and Scan combinators.

  • Fold.classify / classifyScan (and the IO variants): if a key's fold finished on that key's first input, the key wasn't recorded as done, so the next input with that key started a new fold. For example, take 1 sum on [("A",1),("A",2)] emitted two results instead of one.
  • Fold.distributeScan: a fold that finished on an input only showed up in the output on the next input, and was lost if the stream ended there.
  • Fold.sampleFromthen: the offset was applied in the wrong direction. sampleFromthen 1 3 [0..9] gave [2,5,8] instead of [1,4,7]. The old test used offset 0, where both give the same answer, so it didn't catch this.
  • Scan.unzip (so also ***, first and second in the Arrow instance): it crashed with fromJust Nothing whenever one side skipped an input. It now drops those outputs, as the docs say it does.
  • Docs fixes:
    • The Scan module example had the compose arguments swapped.
    • The demuxKvToMap definition was wrong.
    • The sum doc had the numerical stability claim backwards.
    • The minimum doc called its monoid Max.
    • The null doc gave a definition that returns False on an empty stream, where null returns True.
  • The windowRange error message now says windowRange instead of ringsOf.
  • One unrelated CI fix

The macOS GHC bindists hardcode the CLT SDK path of their build
machine: the rts and ghci package confs of GHC 9.14.1 have
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib in
library-dirs. The linker therefore uses the CLT SDK even with Xcode
selected. The macos-26-arm64 image 20261013 ships CLT SDK 27.0, whose
tbd files Xcode 26.6's ld rejects with "unknown architecture
arm64e.x1-macos". The previous image had an older CLT SDK, and a GHC
restored from the cache does not help on branches without that cache.

Point the CLT MacOSX.sdk symlink at the Xcode SDK, and correct the
comment that said GHC records the SDK path when it is installed.
sampleFromthen offset stride kept the elements at the indices i with
(i + offset) mod stride == 0, so sampleFromthen 1 3 on [0..9] returned
[2,5,8] instead of [1,4,7]. Keep the indices i >= offset with
(i - offset) mod stride == 0. The existing test used offset 0, where the
two agree.
distributeScan kept a fold whose step returned Done in the list of
running folds, with Done as its initial action. Its result was added to
the output only on the next input, and was lost if the stream ended:
with the folds [Fold.take 2 Fold.sum, Fold.take 2 Fold.length] on
[1,2], Fold.fromScanl (distributeScan gen) returned [] instead of
[3,2].
The sum doc said that sum is not numerically stable and that foldl' (+) 0
is. sum uses Kahan-Babuska-Neumaier summation, foldl' (+) 0 is the
unstable one. The Min monoid definition in the minimum doc was named
maximum. The definition in the null doc, fmap isJust Fold.one, returns
False for an empty stream, null returns True.
windowRange 0, and windowMinimum 0 and windowMaximum 0, failed with the
message "ringsOf: window size must be > 0".
When the fold for a key terminated on the first input of that key, the
key was neither kept in the map of running folds nor added to the set of
finished keys. The next input with the same key started the fold again
and produced another result. For example, classifyScan fst with the
inner fold "take 1 sum" on [("A",1),("A",2)] emitted ("A",1) and
("A",2) instead of only ("A",1).

Fix classifyGeneric, classifyGenericIO, classifyScanGeneric and
classifyScanGenericIO, and with them classify, classifyIO, classifyScan
and classifyScanIO.
unzip applied fromJust to the result of checking that both scans
produced an output, so it failed with "fromJust Nothing" whenever either
scan skipped an input, although its doc says such outputs are filtered
out. unzip implements (***) in the Arrow instance of Scan, and through it
first and second. For example, scanr (Scan.filter odd *** Scan.identity)
on [(1,1),(2,2)] failed on (2,2) instead of returning [(1,1)].

Filter out the Nothing outputs before fromJust, as teeWith does.
In the Streamly.Data.Scan example, "Scan.compose scan1 scan3" runs scan3
first, so it computed (x^2 + 3x)^2 instead of the stated x^4 + 3x^2.
Swap the arguments and run main in the example to show the output.

The demuxKvToMap definition applied "Fold.lmap snd" to the monadic Maybe
result of the key function instead of to the fold inside it.
@harendra-kumar
harendra-kumar merged commit 8561d61 into master Oct 15, 2026
18 of 19 checks passed
@harendra-kumar
harendra-kumar deleted the fold-scan-fixes branch October 21, 2026 11:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant