Skip to content

[compat] kyo-compat CStream: tag-free, uniformly invariant portable surface#1650

Open
fwbrasil wants to merge 1 commit into
mainfrom
kyo-compat-cstream-tagfree
Open

[compat] kyo-compat CStream: tag-free, uniformly invariant portable surface#1650
fwbrasil wants to merge 1 commit into
mainfrom
kyo-compat-cstream-tagfree

Conversation

@fwbrasil
Copy link
Copy Markdown
Collaborator

Problem

The streams surface promises portability: code written against CStream compiles unchanged against every binding. The kyo binding silently broke that. kyo.Stream keys its emit channel by a Tag[Emit[Chunk[A]]] re-summoned on every operator, and our inline delegations forwarded that requirement to callers, so generic element-abstract pipelines compiled against ce/zio/future/ox/twitter but not against the binding wrapping Kyo's own stream. Separately, the bindings disagreed on variance (CStream[+A] everywhere, but kyo cannot be covariant; see below), which breaks portability the other way.

Solution

Replace the kyo opaque alias with a small invariant final class CStream[A] that captures the Tag once at construction (where A is concrete) and threads it into each delegated kyo.Stream call. Element-preserving operators (take/drop/filter/run/fold/concat/...) need no Tag; element-changing ones (map/mapPure/flatMap/collectPure) take the output Tag[Emit[Chunk[B]]], which derives automatically for concrete B. Make all other bindings invariant too (CStream[A]) so the portable surface agrees on variance.

Notes

  • The kyo carrier is richer than the others: a lowered kyo.Stream is a raw computation that can carry several independent effect channels keyed by element type. The stored tag must stay matched to that carrier, which is why the binding is invariant and lower needs no re-tagging and no casts. The other bindings' carriers are covariant, so dropping + is a pure annotation change; all 38 shared CStreamTest cases still pass on every binding.
  • A root fix in kyo.Stream (dropping the per-operator Tag) was rejected: it exposes that raw multi-channel emit computation, where the element-typed tag is load-bearing across Emit/Poll/Pipe/Sink.
  • CStreamTagFreeTest (kyo binding) covers it: previously-uncompilable generic pipelines, channel isolation across flatMap chains, faithful lower after an element change, plus assertCompiles/assertDoesNotCompile for the tag boundary and invariance.

…urface

### Problem

The streams surface promises portability: code written against `CStream` compiles unchanged against every binding. The kyo binding silently broke that. `kyo.Stream` keys its emit channel by a `Tag[Emit[Chunk[A]]]` re-summoned on every operator, and our inline delegations forwarded that requirement to callers, so generic element-abstract pipelines compiled against ce/zio/future/ox/twitter but not against the binding wrapping Kyo's own stream. Separately, the bindings disagreed on variance (`CStream[+A]` everywhere, but kyo cannot be covariant; see below), which breaks portability the other way.

### Solution

Replace the kyo opaque alias with a small invariant `final class CStream[A]` that captures the `Tag` once at construction (where `A` is concrete) and threads it into each delegated `kyo.Stream` call. Element-preserving operators (`take`/`drop`/`filter`/`run`/`fold`/`concat`/...) need no `Tag`; element-changing ones (`map`/`mapPure`/`flatMap`/`collectPure`) take the output `Tag[Emit[Chunk[B]]]`, which derives automatically for concrete `B`. Make all other bindings invariant too (`CStream[A]`) so the portable surface agrees on variance.

### Notes

- The kyo carrier is richer than the others: a lowered `kyo.Stream` is a raw computation that can carry several independent effect channels keyed by element type. The stored tag must stay matched to that carrier, which is why the binding is invariant and `lower` needs no re-tagging and no casts. The other bindings' carriers are covariant, so dropping `+` is a pure annotation change; all 38 shared `CStreamTest` cases still pass on every binding.
- A root fix in `kyo.Stream` (dropping the per-operator `Tag`) was rejected: it exposes that raw multi-channel emit computation, where the element-typed tag is load-bearing across `Emit`/`Poll`/`Pipe`/`Sink`.
- `CStreamTagFreeTest` (kyo binding) covers it: previously-uncompilable generic pipelines, channel isolation across flatMap chains, faithful `lower` after an element change, plus `assertCompiles`/`assertDoesNotCompile` for the tag boundary and invariance.

/** Flat-maps each element to another stream and concatenates the results. */
def flatMap[B](f: A => CStream[B])(using outTag: Tag[Emit[Chunk[B]]], frame: Frame): CStream[B] =
given tagA: Tag[Emit[Chunk[A]]] = tag
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we make this tag given in the class?

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.

2 participants