Remix.run Logo
lucideer 6 hours ago

Other angles of critique & consideration already covered well by sibling commenters. One extra consideration (unrelated to streams, more general) is the API design & dev UX/DX:

  type Stream<T> = {
    next(): { done, value: T } | Promise<{ done, value: T }>
  }
the above can effectively be discussed as a combination of the following:

  type Stream<T> = {
    next(): { done, value: T }
  }
  type Stream<T> = {
    next(): Promise<{ done, value: T }>
  }

You've covered the justifications for the 2nd signature, but it's a messy API. Specifically:

> My way, if I define a sync transform over a sync input, the whole iteration can be sync making it possible to get and use the result in sync functions. This is huge as otherwise you have to write all the code twice: once with sync iterator and for loops and once with async iterators and for await loops.

Writing all the code twice is cleaner in every implementation scenario I can envisage. It's very rare I want generalised flexibility on an API call - that leads to a lot of confusion & ambiguity when reading/reviewing code, & also when adding to/editing code. Any repetitiveness in handling both use-cases (separately) can easily be handled with well thought-out composition.

conartist6 5 hours ago | parent [-]

How is it cleaner? I used to actually do that. I wrote everything twice. I even built fancy tools to help me write everything twice.

But the bigger problem here is that sync and async aren't enough. You almost need to write everything three times: sync, async, and async-batched. And that async-batched code is gonna be gnarly and different from the other two copies and writing it in the first place and keeping it in sync is gonna give you headaches.

To see how it played out for me take a look at the difference between:

https://github.com/iter-tools/regex/blob/a35a0259bf288ccece2... https://github.com/iter-tools/regex/blob/a35a0259bf288ccece2... https://github.com/iter-tools/regex/blob/a35a0259bf288ccece2...