unisonweb / base

Unison base libraries

Home Page:https://share.unison-lang.org/@unison/base

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

It's easy to silently mess up Stream.flatMap calls

ceedubs opened this issue · comments

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.