It's easy to silently mess up Stream.flatMap calls
This is a fun one that I just ran into. What is the result of this watch expression?
> Stream.fromList ["foo", "bar"]
|> Stream.flatMap (s -> Stream.fromList (toCharList s))
|> Stream.toList!
If you guessed [] then you are right!
The issue is that the code should use Stream.fromList! (toCharList s) instead of Stream.fromList (toCharList s).
Let's look at the signature of Stream.flatMap:
Stream.flatMap :
(a ->{e, Stream b} any) -> '{e, Stream a} r -> '{e, Stream b} r
In the example, the function that we are passing in has the signature Char -> {} ('{Stream Char} ). So Stream.flatMap views this as a function that takes a Char, doesn't emit any stream elements, and then returns an ignored value which happens to be a thunk that would emit a stream of characters should you run it instead of ignore it.
I think that a simple solution would be to make Stream.flatMap require () as the return type of the mapping function:
Stream.flatMap :
(a ->{e, Stream b} ()) -> '{e, Stream a} r -> '{e, Stream b} r
This means that occasionally you might need to add an ignore to your function, but I think that's a small price to pay to avoid silent issues that cause your code to not at all do what you would expect.
NOTE: the same applies to Stream.flatMap!.
cc @anovstrup who has done a lot of great Stream work.
Hah I now retract my "Sounds fine to me" that I commented here.