Skip to main content

hydro_lang/live_collections/keyed_stream/
mod.rs

1//! Definitions for the [`KeyedStream`] live collection.
2
3use std::cell::RefCell;
4use std::collections::HashMap;
5use std::hash::Hash;
6use std::marker::PhantomData;
7use std::ops::Deref;
8use std::rc::Rc;
9
10use stageleft::{IntoQuotedMut, QuotedWithContext, QuotedWithContextWithProps, q};
11
12use super::OperatorContext;
13use super::boundedness::{Bounded, Boundedness, IsBounded, Unbounded};
14use super::keyed_singleton::KeyedSingleton;
15use super::optional::Optional;
16use super::stream::{
17    ExactlyOnce, IsExactlyOnce, IsOrdered, MinOrder, MinRetries, NoOrder, Stream, TotalOrder,
18};
19use crate::compile::builder::{CycleId, FlowState};
20use crate::compile::ir::{
21    CollectionKind, HydroIrOpMetadata, HydroNode, HydroRoot, SharedNode, StreamOrder, StreamRetry,
22};
23#[cfg(stageleft_runtime)]
24use crate::forward_handle::{CycleCollection, ReceiverComplete};
25use crate::forward_handle::{ForwardRef, TickCycle};
26use crate::live_collections::batch_atomic::BatchAtomic;
27use crate::live_collections::keyed_singleton::KeyedSingletonBound;
28use crate::live_collections::stream::{
29    AtLeastOnce, Ordering, Retries, WeakerOrderingThan, WeakerRetryThan,
30};
31#[cfg(stageleft_runtime)]
32use crate::location::dynamic::{DynLocation, LocationId};
33use crate::location::tick::DeferTick;
34use crate::location::{Atomic, Location, Tick, TopLevel, check_matching_location};
35use crate::manual_expr::ManualExpr;
36use crate::nondet::{NonDet, nondet};
37use crate::properties::{
38    AggFuncAlgebra, ApplyMonotoneKeyedStream, ValidCommutativityFor, ValidIdempotenceFor,
39    manual_proof,
40};
41
42pub mod networking;
43
44/// Streaming elements of type `V` grouped by a key of type `K`.
45///
46/// Keyed Streams capture streaming elements of type `V` grouped by a key of type `K`, where the
47/// order of keys is non-deterministic but the order *within* each group may be deterministic.
48///
49/// Although keyed streams are conceptually grouped by keys, values are not immediately grouped
50/// into buckets when constructing a keyed stream. Instead, keyed streams defer grouping until an
51/// operator such as [`KeyedStream::fold`] is called, which requires `K: Hash + Eq`.
52///
53/// Type Parameters:
54/// - `K`: the type of the key for each group
55/// - `V`: the type of the elements inside each group
56/// - `Loc`: the [`Location`] where the keyed stream is materialized
57/// - `Bound`: tracks whether the entries are [`Bounded`] (local and finite) or [`Unbounded`] (asynchronous and possibly infinite)
58/// - `Order`: tracks whether the elements within each group have deterministic order
59///   ([`TotalOrder`]) or not ([`NoOrder`])
60/// - `Retries`: tracks whether the elements within each group have deterministic cardinality
61///   ([`ExactlyOnce`]) or may have non-deterministic retries ([`crate::live_collections::stream::AtLeastOnce`])
62pub struct KeyedStream<
63    K,
64    V,
65    Loc,
66    Bound: Boundedness = Unbounded,
67    Order: Ordering = TotalOrder,
68    Retry: Retries = ExactlyOnce,
69> {
70    pub(crate) location: Loc,
71    pub(crate) ir_node: Rc<RefCell<HydroNode>>,
72    pub(crate) flow_state: FlowState,
73
74    _phantom: PhantomData<(K, V, Loc, Bound, Order, Retry)>,
75}
76
77impl<K, V, L, B: Boundedness, O: Ordering, R: Retries> Drop for KeyedStream<K, V, L, B, O, R> {
78    fn drop(&mut self) {
79        let ir_node = self.ir_node.replace(HydroNode::Placeholder);
80        if !matches!(ir_node, HydroNode::Placeholder) && !ir_node.is_shared_with_others() {
81            self.flow_state.borrow_mut().try_push_root(HydroRoot::Null {
82                input: Box::new(ir_node),
83                op_metadata: HydroIrOpMetadata::new(),
84            });
85        }
86    }
87}
88
89impl<'a, K, V, L, O: Ordering, R: Retries> From<KeyedStream<K, V, L, Bounded, O, R>>
90    for KeyedStream<K, V, L, Unbounded, O, R>
91where
92    L: Location<'a>,
93{
94    fn from(stream: KeyedStream<K, V, L, Bounded, O, R>) -> KeyedStream<K, V, L, Unbounded, O, R> {
95        let new_meta = stream
96            .location
97            .new_node_metadata(KeyedStream::<K, V, L, Unbounded, O, R>::collection_kind());
98
99        let flow_state = stream.flow_state.clone();
100        KeyedStream {
101            location: stream.location.clone(),
102            ir_node: crate::live_collections::tracked_ir_node(
103                &flow_state,
104                HydroNode::Cast {
105                    inner: Box::new(stream.ir_node.replace(HydroNode::Placeholder)),
106                    metadata: new_meta,
107                },
108            ),
109            flow_state,
110            _phantom: PhantomData,
111        }
112    }
113}
114
115impl<'a, K, V, L, B: Boundedness, R: Retries> From<KeyedStream<K, V, L, B, TotalOrder, R>>
116    for KeyedStream<K, V, L, B, NoOrder, R>
117where
118    L: Location<'a>,
119{
120    fn from(stream: KeyedStream<K, V, L, B, TotalOrder, R>) -> KeyedStream<K, V, L, B, NoOrder, R> {
121        stream.weaken_ordering()
122    }
123}
124
125impl<'a, K, V, L, O: Ordering, R: Retries> DeferTick for KeyedStream<K, V, Tick<L>, Bounded, O, R>
126where
127    L: Location<'a>,
128{
129    fn defer_tick(self) -> Self {
130        KeyedStream::defer_tick(self)
131    }
132}
133
134impl<'a, K, V, L, O: Ordering, R: Retries> CycleCollection<'a, TickCycle>
135    for KeyedStream<K, V, Tick<L>, Bounded, O, R>
136where
137    L: Location<'a>,
138{
139    type Location = Tick<L>;
140
141    fn create_source(cycle_id: CycleId, location: Tick<L>) -> Self {
142        let flow_state = location.flow_state().clone();
143        KeyedStream {
144            ir_node: crate::live_collections::tracked_ir_node(
145                &flow_state,
146                HydroNode::CycleSource {
147                    cycle_id,
148                    metadata: location.new_node_metadata(
149                        KeyedStream::<K, V, Tick<L>, Bounded, O, R>::collection_kind(),
150                    ),
151                },
152            ),
153            flow_state,
154            location,
155            _phantom: PhantomData,
156        }
157    }
158}
159
160impl<'a, K, V, L, O: Ordering, R: Retries> ReceiverComplete<'a, TickCycle>
161    for KeyedStream<K, V, Tick<L>, Bounded, O, R>
162where
163    L: Location<'a>,
164{
165    fn complete(self, cycle_id: CycleId, expected_location: LocationId) {
166        assert_eq!(
167            Location::id(&self.location),
168            expected_location,
169            "locations do not match"
170        );
171
172        self.location
173            .flow_state()
174            .borrow_mut()
175            .push_root(HydroRoot::CycleSink {
176                cycle_id,
177                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
178                op_metadata: HydroIrOpMetadata::new(),
179            });
180    }
181}
182
183impl<'a, K, V, L, B: Boundedness, O: Ordering, R: Retries> CycleCollection<'a, ForwardRef>
184    for KeyedStream<K, V, L, B, O, R>
185where
186    L: Location<'a>,
187{
188    type Location = L;
189
190    fn create_source(cycle_id: CycleId, location: L) -> Self {
191        let flow_state = location.flow_state().clone();
192        KeyedStream {
193            ir_node: crate::live_collections::tracked_ir_node(
194                &flow_state,
195                HydroNode::CycleSource {
196                    cycle_id,
197                    metadata: location
198                        .new_node_metadata(KeyedStream::<K, V, L, B, O, R>::collection_kind()),
199                },
200            ),
201            flow_state,
202            location,
203            _phantom: PhantomData,
204        }
205    }
206}
207
208impl<'a, K, V, L, B: Boundedness, O: Ordering, R: Retries> ReceiverComplete<'a, ForwardRef>
209    for KeyedStream<K, V, L, B, O, R>
210where
211    L: Location<'a>,
212{
213    fn complete(self, cycle_id: CycleId, expected_location: LocationId) {
214        assert_eq!(
215            Location::id(&self.location),
216            expected_location,
217            "locations do not match"
218        );
219        self.location
220            .flow_state()
221            .borrow_mut()
222            .push_root(HydroRoot::CycleSink {
223                cycle_id,
224                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
225                op_metadata: HydroIrOpMetadata::new(),
226            });
227    }
228}
229
230impl<'a, K: Clone, V: Clone, Loc: Location<'a>, Bound: Boundedness, Order: Ordering, R: Retries>
231    Clone for KeyedStream<K, V, Loc, Bound, Order, R>
232{
233    fn clone(&self) -> Self {
234        if !matches!(self.ir_node.borrow().deref(), HydroNode::Tee { .. }) {
235            let orig_ir_node = self.ir_node.replace(HydroNode::Placeholder);
236            *self.ir_node.borrow_mut() = HydroNode::Tee {
237                inner: SharedNode(Rc::new(RefCell::new(orig_ir_node))),
238                metadata: self.location.new_node_metadata(Self::collection_kind()),
239            };
240        }
241
242        if let HydroNode::Tee { inner, metadata } = self.ir_node.borrow().deref() {
243            KeyedStream {
244                location: self.location.clone(),
245                flow_state: self.flow_state.clone(),
246                ir_node: crate::live_collections::tracked_ir_node(
247                    &self.flow_state,
248                    HydroNode::Tee {
249                        inner: SharedNode(inner.0.clone()),
250                        metadata: metadata.clone(),
251                    },
252                ),
253                _phantom: PhantomData,
254            }
255        } else {
256            unreachable!()
257        }
258    }
259}
260
261/// The output of a Hydro generator created with [`KeyedStream::generator`], which can yield elements and
262/// control the processing of future elements.
263pub enum Generate<T> {
264    /// Emit the provided element, and keep processing future inputs.
265    Yield(T),
266    /// Emit the provided element as the _final_ element, do not process future inputs.
267    Return(T),
268    /// Do not emit anything, but continue processing future inputs.
269    Continue,
270    /// Do not emit anything, and do not process further inputs.
271    Break,
272}
273
274impl<'a, K, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
275    KeyedStream<K, V, L, B, O, R>
276{
277    pub(crate) fn new(location: L, ir_node: HydroNode) -> Self {
278        debug_assert_eq!(ir_node.metadata().location_id, Location::id(&location));
279        debug_assert_eq!(ir_node.metadata().collection_kind, Self::collection_kind());
280
281        let flow_state = location.flow_state().clone();
282        let ir_node = crate::live_collections::tracked_ir_node(&flow_state, ir_node);
283        KeyedStream {
284            location,
285            flow_state,
286            ir_node,
287            _phantom: PhantomData,
288        }
289    }
290
291    /// Returns the [`CollectionKind`] corresponding to this type.
292    pub fn collection_kind() -> CollectionKind {
293        CollectionKind::KeyedStream {
294            bound: B::BOUND_KIND,
295            value_order: O::ORDERING_KIND,
296            value_retry: R::RETRIES_KIND,
297            key_type: stageleft::quote_type::<K>().into(),
298            value_type: stageleft::quote_type::<V>().into(),
299        }
300    }
301
302    /// Returns the [`Location`] where this keyed stream is being materialized.
303    pub fn location(&self) -> &L {
304        &self.location
305    }
306
307    /// Weakens the consistency of this live collection to not guarantee any consistency across
308    /// cluster members (if this collection is on a cluster).
309    pub fn weaken_consistency(self) -> KeyedStream<K, V, L::DropConsistency, B, O, R>
310    where
311        L: Location<'a>,
312    {
313        if L::consistency()
314            .is_none_or(|c| c == crate::location::dynamic::ClusterConsistency::NoConsistency)
315        {
316            // already no consistency
317            KeyedStream::new(
318                self.location.drop_consistency(),
319                self.ir_node.replace(HydroNode::Placeholder),
320            )
321        } else {
322            KeyedStream::new(
323                self.location.drop_consistency(),
324                HydroNode::Cast {
325                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
326                    metadata: self
327                        .location
328                        .drop_consistency()
329                        .new_node_metadata(
330                            KeyedStream::<K, V, L::DropConsistency, B>::collection_kind(),
331                        ),
332                },
333            )
334        }
335    }
336
337    /// Casts this live collection to have the consistency guarantees specified in the given
338    /// location type parameter. The developer must ensure that the strengthened consistency
339    /// is actually guaranteed, via the proof field (see [`crate::prelude::manual_proof`]).
340    pub fn assert_has_consistency_of<L2: Location<'a, DropConsistency = L::DropConsistency>>(
341        self,
342        _proof: impl crate::properties::ConsistencyProof,
343    ) -> KeyedStream<K, V, L2, B, O, R>
344    where
345        L: Location<'a>,
346    {
347        if L::consistency() == L2::consistency() {
348            KeyedStream::new(
349                self.location.with_consistency_of(),
350                self.ir_node.replace(HydroNode::Placeholder),
351            )
352        } else {
353            KeyedStream::new(
354                self.location.with_consistency_of(),
355                HydroNode::AssertIsConsistent {
356                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
357                    trusted: false,
358                    metadata: self
359                        .location
360                        .clone()
361                        .with_consistency_of::<L2>()
362                        .new_node_metadata(KeyedStream::<K, V, L2, B, O, R>::collection_kind()),
363                },
364            )
365        }
366    }
367
368    pub(crate) fn assert_has_consistency_of_trusted<
369        L2: Location<'a, DropConsistency = L::DropConsistency>,
370    >(
371        self,
372        _proof: impl crate::properties::ConsistencyProof,
373    ) -> KeyedStream<K, V, L2, B, O, R>
374    where
375        L: Location<'a>,
376    {
377        if L::consistency() == L2::consistency() {
378            KeyedStream::new(
379                self.location.with_consistency_of(),
380                self.ir_node.replace(HydroNode::Placeholder),
381            )
382        } else {
383            KeyedStream::new(
384                self.location.with_consistency_of(),
385                HydroNode::AssertIsConsistent {
386                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
387                    trusted: true,
388                    metadata: self
389                        .location
390                        .clone()
391                        .with_consistency_of::<L2>()
392                        .new_node_metadata(KeyedStream::<K, V, L2, B, O, R>::collection_kind()),
393                },
394            )
395        }
396    }
397
398    /// Turns this [`KeyedStream`] into a [`Stream`] preserving ordering, under the invariant
399    /// assumption that there is at most one key. If this invariant is broken, the program
400    /// may exhibit undefined behavior, so uses must be carefully vetted.
401    pub(crate) fn cast_at_most_one_key(self) -> Stream<(K, V), L, B, O, R> {
402        Stream::new(
403            self.location.clone(),
404            HydroNode::Cast {
405                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
406                metadata: self
407                    .location
408                    .new_node_metadata(Stream::<(K, V), L, B, O, R>::collection_kind()),
409            },
410        )
411    }
412
413    /// Turns this [`KeyedStream`] into a [`KeyedSingleton`], under the invariant assumption that
414    /// there is at most one entry per key. If this invariant is broken, the program may exhibit
415    /// undefined behavior, so uses must be carefully vetted.
416    pub(crate) fn cast_at_most_one_entry_per_key(
417        self,
418    ) -> KeyedSingleton<K, V, L, B::WithBoundedValue> {
419        KeyedSingleton::new(
420            self.location.clone(),
421            HydroNode::Cast {
422                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
423                metadata: self.location.new_node_metadata(KeyedSingleton::<
424                    K,
425                    V,
426                    L,
427                    B::WithBoundedValue,
428                >::collection_kind()),
429            },
430        )
431    }
432
433    pub(crate) fn use_ordering_type<O2: Ordering>(self) -> KeyedStream<K, V, L, B, O2, R> {
434        if O::ORDERING_KIND == O2::ORDERING_KIND {
435            KeyedStream::new(
436                self.location.clone(),
437                self.ir_node.replace(HydroNode::Placeholder),
438            )
439        } else {
440            panic!(
441                "Runtime ordering {:?} did not match requested cast {:?}.",
442                O::ORDERING_KIND,
443                O2::ORDERING_KIND
444            )
445        }
446    }
447
448    /// Explicitly "casts" the keyed stream to a type with a different ordering
449    /// guarantee for each group. Useful in unsafe code where the ordering cannot be proven
450    /// by the type-system.
451    ///
452    /// # Non-Determinism
453    /// This function is used as an escape hatch, and any mistakes in the
454    /// provided ordering guarantee will propagate into the guarantees
455    /// for the rest of the program.
456    ///
457    /// In simulation tests, the ordering decisions can be scripted by attaching a
458    /// [`KeyedOrderingHook`](crate::sim_hooks::KeyedOrderingHook) to the guard via
459    /// `nondet!(/** reason */ hook = my_hook)`.
460    pub fn assume_ordering<O2: Ordering>(
461        self,
462        mut nondet: NonDet<Option<crate::sim_hooks::KeyedOrderingHook<K, V, B>>>,
463    ) -> KeyedStream<K, V, L::DropConsistency, B, O2, R> {
464        if O::ORDERING_KIND == O2::ORDERING_KIND {
465            self.use_ordering_type().weaken_consistency()
466        } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
467            // We can always weaken the ordering guarantee
468            let target_location = self.location.drop_consistency();
469            KeyedStream::new(
470                target_location.clone(),
471                HydroNode::Cast {
472                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
473                    metadata: target_location
474                        .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind()),
475                },
476            )
477        } else {
478            let target_location = self.location.drop_consistency();
479            let mut metadata = target_location
480                .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind());
481            metadata.op.sim_hook_id = nondet.take_hook().map(|hook| hook.id);
482            KeyedStream::new(
483                target_location,
484                HydroNode::ObserveNonDet {
485                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
486                    trusted: false,
487                    metadata,
488                },
489            )
490        }
491    }
492
493    fn assume_ordering_trusted<O2: Ordering>(
494        self,
495        _nondet: NonDet,
496    ) -> KeyedStream<K, V, L, B, O2, R> {
497        if O::ORDERING_KIND == O2::ORDERING_KIND {
498            KeyedStream::new(
499                self.location.clone(),
500                self.ir_node.replace(HydroNode::Placeholder),
501            )
502        } else if O2::ORDERING_KIND == StreamOrder::NoOrder {
503            // We can always weaken the ordering guarantee
504            KeyedStream::new(
505                self.location.clone(),
506                HydroNode::Cast {
507                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
508                    metadata: self
509                        .location
510                        .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind()),
511                },
512            )
513        } else {
514            KeyedStream::new(
515                self.location.clone(),
516                HydroNode::ObserveNonDet {
517                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
518                    trusted: true,
519                    metadata: self
520                        .location
521                        .new_node_metadata(KeyedStream::<K, V, L, B, O2, R>::collection_kind()),
522                },
523            )
524        }
525    }
526
527    #[deprecated = "use `weaken_ordering::<NoOrder>()` instead"]
528    /// Weakens the ordering guarantee provided by the stream to [`NoOrder`],
529    /// which is always safe because that is the weakest possible guarantee.
530    pub fn weakest_ordering(self) -> KeyedStream<K, V, L, B, NoOrder, R> {
531        self.weaken_ordering::<NoOrder>()
532    }
533
534    /// Weakens the ordering guarantee provided by the stream to `O2`, with the type-system
535    /// enforcing that `O2` is weaker than the input ordering guarantee.
536    pub fn weaken_ordering<O2: WeakerOrderingThan<O>>(self) -> KeyedStream<K, V, L, B, O2, R> {
537        let nondet = nondet!(/** this is a weaker ordering guarantee, so it is safe to assume */);
538        self.assume_ordering_trusted::<O2>(nondet)
539    }
540
541    /// Strengthens the ordering guarantee to `TotalOrder`, given that `O: IsOrdered`, which
542    /// implies that `O == TotalOrder`.
543    pub fn make_totally_ordered(self) -> KeyedStream<K, V, L, B, TotalOrder, R>
544    where
545        O: IsOrdered,
546    {
547        self.assume_ordering_trusted(nondet!(/** no-op */))
548    }
549
550    /// Explicitly "casts" the keyed stream to a type with a different retries
551    /// guarantee for each group. Useful in unsafe code where the lack of retries cannot
552    /// be proven by the type-system.
553    ///
554    /// # Non-Determinism
555    /// This function is used as an escape hatch, and any mistakes in the
556    /// provided retries guarantee will propagate into the guarantees
557    /// for the rest of the program.
558    pub fn assume_retries<R2: Retries>(
559        self,
560        _nondet: NonDet,
561    ) -> KeyedStream<K, V, L::DropConsistency, B, O, R2> {
562        if R::RETRIES_KIND == R2::RETRIES_KIND {
563            KeyedStream::new(
564                self.location.drop_consistency(),
565                self.ir_node.replace(HydroNode::Placeholder),
566            )
567        } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
568            // We can always weaken the retries guarantee
569            let target_location = self.location.drop_consistency();
570            KeyedStream::new(
571                target_location.clone(),
572                HydroNode::Cast {
573                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
574                    metadata: target_location
575                        .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
576                },
577            )
578        } else {
579            let target_location = self.location.drop_consistency();
580            KeyedStream::new(
581                target_location.clone(),
582                HydroNode::ObserveNonDet {
583                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
584                    trusted: false,
585                    metadata: target_location
586                        .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
587                },
588            )
589        }
590    }
591
592    // only for internal APIs that have been carefully vetted to ensure that the non-determinism
593    // is not observable
594    fn assume_retries_trusted<R2: Retries>(
595        self,
596        _nondet: NonDet,
597    ) -> KeyedStream<K, V, L, B, O, R2> {
598        if R::RETRIES_KIND == R2::RETRIES_KIND {
599            KeyedStream::new(
600                self.location.clone(),
601                self.ir_node.replace(HydroNode::Placeholder),
602            )
603        } else if R2::RETRIES_KIND == StreamRetry::AtLeastOnce {
604            // We can always weaken the retries guarantee
605            KeyedStream::new(
606                self.location.clone(),
607                HydroNode::Cast {
608                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
609                    metadata: self
610                        .location
611                        .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
612                },
613            )
614        } else {
615            KeyedStream::new(
616                self.location.clone(),
617                HydroNode::ObserveNonDet {
618                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
619                    trusted: true,
620                    metadata: self
621                        .location
622                        .new_node_metadata(KeyedStream::<K, V, L, B, O, R2>::collection_kind()),
623                },
624            )
625        }
626    }
627
628    #[deprecated = "use `weaken_retries::<AtLeastOnce>()` instead"]
629    /// Weakens the retries guarantee provided by the stream to [`AtLeastOnce`],
630    /// which is always safe because that is the weakest possible guarantee.
631    pub fn weakest_retries(self) -> KeyedStream<K, V, L, B, O, AtLeastOnce> {
632        self.weaken_retries::<AtLeastOnce>()
633    }
634
635    /// Weakens the retries guarantee provided by the stream to `R2`, with the type-system
636    /// enforcing that `R2` is weaker than the input retries guarantee.
637    pub fn weaken_retries<R2: WeakerRetryThan<R>>(self) -> KeyedStream<K, V, L, B, O, R2> {
638        let nondet = nondet!(/** this is a weaker retries guarantee, so it is safe to assume */);
639        self.assume_retries_trusted::<R2>(nondet)
640    }
641
642    /// Strengthens the retry guarantee to `ExactlyOnce`, given that `R: IsExactlyOnce`, which
643    /// implies that `R == ExactlyOnce`.
644    pub fn make_exactly_once(self) -> KeyedStream<K, V, L, B, O, ExactlyOnce>
645    where
646        R: IsExactlyOnce,
647    {
648        self.assume_retries_trusted(nondet!(/** no-op */))
649    }
650
651    /// Strengthens the boundedness guarantee to `Bounded`, given that `B: IsBounded`, which
652    /// implies that `B == Bounded`.
653    pub fn make_bounded(self) -> KeyedStream<K, V, L, Bounded, O, R>
654    where
655        B: IsBounded,
656    {
657        self.weaken_boundedness()
658    }
659
660    /// Weakens the boundedness guarantee to an arbitrary boundedness `B2`, given that `B: IsBounded`,
661    /// which implies that `B == Bounded`.
662    pub fn weaken_boundedness<B2: Boundedness>(self) -> KeyedStream<K, V, L, B2, O, R> {
663        if B::BOUNDED == B2::BOUNDED {
664            KeyedStream::new(
665                self.location.clone(),
666                self.ir_node.replace(HydroNode::Placeholder),
667            )
668        } else {
669            // We can always weaken the boundedness
670            KeyedStream::new(
671                self.location.clone(),
672                HydroNode::Cast {
673                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
674                    metadata: self
675                        .location
676                        .new_node_metadata(KeyedStream::<K, V, L, B2, O, R>::collection_kind()),
677                },
678            )
679        }
680    }
681
682    /// Flattens the keyed stream into an unordered stream of key-value pairs.
683    ///
684    /// # Example
685    /// ```rust
686    /// # #[cfg(feature = "deploy")] {
687    /// # use hydro_lang::prelude::*;
688    /// # use futures::StreamExt;
689    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
690    /// process
691    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
692    ///     .into_keyed()
693    ///     .entries()
694    /// # }, |mut stream| async move {
695    /// // (1, 2), (1, 3), (2, 4) in any order
696    /// # let mut results = Vec::new();
697    /// # for _ in 0..3 {
698    /// #     results.push(stream.next().await.unwrap());
699    /// # }
700    /// # results.sort();
701    /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4)]);
702    /// # }));
703    /// # }
704    /// ```
705    pub fn entries(self) -> Stream<(K, V), L, B, NoOrder, R> {
706        Stream::new(
707            self.location.clone(),
708            HydroNode::Cast {
709                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
710                metadata: self
711                    .location
712                    .new_node_metadata(Stream::<(K, V), L, B, NoOrder, R>::collection_kind()),
713            },
714        )
715    }
716
717    /// Flattens the keyed stream into a totally ordered stream of key-value pairs,
718    /// preserving the order of values within each key group but non-deterministically
719    /// interleaving across keys.
720    ///
721    /// Requires the keyed stream to be totally ordered within each group (`O: IsOrdered`).
722    ///
723    /// # Non-Determinism
724    /// The interleaving of entries across different keys is non-deterministic.
725    /// Within each key, the original order is preserved.
726    ///
727    /// In simulation tests, the interleaving decisions can be scripted by attaching a
728    /// [`PartialOrderingHook`](crate::sim_hooks::PartialOrderingHook) to the guard via
729    /// `nondet!(/** reason */ hook = my_hook)`.
730    pub fn entries_partially_ordered(
731        self,
732        mut nondet: NonDet<Option<crate::sim_hooks::PartialOrderingHook<K, V, B>>>,
733    ) -> Stream<(K, V), L::DropConsistency, B, TotalOrder, R>
734    where
735        O: IsOrdered,
736    {
737        let target_location = self.location.drop_consistency();
738        let mut metadata = target_location
739            .new_node_metadata(Stream::<(K, V), L, B, TotalOrder, R>::collection_kind());
740        metadata.op.sim_hook_id = nondet.take_hook().map(|hook| hook.id);
741        Stream::new(
742            target_location,
743            HydroNode::ObserveNonDet {
744                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
745                trusted: false,
746                metadata,
747            },
748        )
749    }
750
751    /// Flattens the keyed stream into an unordered stream of only the values.
752    ///
753    /// # Example
754    /// ```rust
755    /// # #[cfg(feature = "deploy")] {
756    /// # use hydro_lang::prelude::*;
757    /// # use futures::StreamExt;
758    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
759    /// process
760    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
761    ///     .into_keyed()
762    ///     .values()
763    /// # }, |mut stream| async move {
764    /// // 2, 3, 4 in any order
765    /// # let mut results = Vec::new();
766    /// # for _ in 0..3 {
767    /// #     results.push(stream.next().await.unwrap());
768    /// # }
769    /// # results.sort();
770    /// # assert_eq!(results, vec![2, 3, 4]);
771    /// # }));
772    /// # }
773    /// ```
774    pub fn values(self) -> Stream<V, L, B, NoOrder, R> {
775        self.entries().map(q!(|(_, v)| v))
776    }
777
778    /// Flattens the keyed stream into an unordered stream of just the keys.
779    ///
780    /// # Example
781    /// ```rust
782    /// # #[cfg(feature = "deploy")] {
783    /// # use hydro_lang::prelude::*;
784    /// # use futures::StreamExt;
785    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
786    /// # process
787    /// #     .source_iter(q!(vec![(1, 2), (2, 4), (1, 5)]))
788    /// #     .into_keyed()
789    /// #     .keys()
790    /// # }, |mut stream| async move {
791    /// // 1, 2 in any order
792    /// # let mut results = Vec::new();
793    /// # for _ in 0..2 {
794    /// #     results.push(stream.next().await.unwrap());
795    /// # }
796    /// # results.sort();
797    /// # assert_eq!(results, vec![1, 2]);
798    /// # }));
799    /// # }
800    /// ```
801    pub fn keys(self) -> Stream<K, L, B, NoOrder, ExactlyOnce>
802    where
803        K: Eq + Hash,
804    {
805        self.entries().map(q!(|(k, _)| k)).unique()
806    }
807
808    /// Transforms each value by invoking `f` on each element, with keys staying the same
809    /// after transformation. If you need access to the key, see [`KeyedStream::map_with_key`].
810    ///
811    /// If you do not want to modify the stream and instead only want to view
812    /// each item use [`KeyedStream::inspect`] instead.
813    ///
814    /// # Example
815    /// ```rust
816    /// # #[cfg(feature = "deploy")] {
817    /// # use hydro_lang::prelude::*;
818    /// # use futures::StreamExt;
819    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
820    /// process
821    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
822    ///     .into_keyed()
823    ///     .map(q!(|v| v + 1))
824    /// #   .entries()
825    /// # }, |mut stream| async move {
826    /// // { 1: [3, 4], 2: [5] }
827    /// # let mut results = Vec::new();
828    /// # for _ in 0..3 {
829    /// #     results.push(stream.next().await.unwrap());
830    /// # }
831    /// # results.sort();
832    /// # assert_eq!(results, vec![(1, 3), (1, 4), (2, 5)]);
833    /// # }));
834    /// # }
835    /// ```
836    pub fn map<U, F>(
837        self,
838        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
839    ) -> KeyedStream<K, U, L, B, O, R>
840    where
841        F: Fn(V) -> U + 'a,
842    {
843        let f: ManualExpr<F, _> =
844            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_ctx(ctx));
845        let map_f = q!({
846            let orig = f;
847            move |(k, v)| (k, orig(v))
848        })
849        .splice_fn1_ctx::<(K, V), (K, U)>(&OperatorContext::<L, B>::new(&self.location))
850        .into();
851
852        KeyedStream::new(
853            self.location.clone(),
854            HydroNode::Map {
855                f: map_f,
856                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
857                metadata: self
858                    .location
859                    .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
860            },
861        )
862    }
863
864    /// Transforms each value by invoking `f` on each key-value pair. The resulting values are **not**
865    /// re-grouped even they are tuples; instead they will be grouped under the original key.
866    ///
867    /// If you do not want to modify the stream and instead only want to view
868    /// each item use [`KeyedStream::inspect_with_key`] instead.
869    ///
870    /// # Example
871    /// ```rust
872    /// # #[cfg(feature = "deploy")] {
873    /// # use hydro_lang::prelude::*;
874    /// # use futures::StreamExt;
875    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
876    /// process
877    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
878    ///     .into_keyed()
879    ///     .map_with_key(q!(|(k, v)| k + v))
880    /// #   .entries()
881    /// # }, |mut stream| async move {
882    /// // { 1: [3, 4], 2: [6] }
883    /// # let mut results = Vec::new();
884    /// # for _ in 0..3 {
885    /// #     results.push(stream.next().await.unwrap());
886    /// # }
887    /// # results.sort();
888    /// # assert_eq!(results, vec![(1, 3), (1, 4), (2, 6)]);
889    /// # }));
890    /// # }
891    /// ```
892    pub fn map_with_key<U, F>(
893        self,
894        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
895    ) -> KeyedStream<K, U, L, B, O, R>
896    where
897        F: Fn((K, V)) -> U + 'a,
898        K: Clone,
899    {
900        let f: ManualExpr<F, _> =
901            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_ctx(ctx));
902        let map_f = q!({
903            let orig = f;
904            move |(k, v)| {
905                let out = orig((Clone::clone(&k), v));
906                (k, out)
907            }
908        })
909        .splice_fn1_ctx::<(K, V), (K, U)>(&OperatorContext::<L, B>::new(&self.location))
910        .into();
911
912        KeyedStream::new(
913            self.location.clone(),
914            HydroNode::Map {
915                f: map_f,
916                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
917                metadata: self
918                    .location
919                    .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
920            },
921        )
922    }
923
924    /// Prepends a new value to the key of each element in the stream, producing a new
925    /// keyed stream with compound keys. Because the original key is preserved, no re-grouping
926    /// occurs and the elements in each group preserve their original order.
927    ///
928    /// # Example
929    /// ```rust
930    /// # #[cfg(feature = "deploy")] {
931    /// # use hydro_lang::prelude::*;
932    /// # use futures::StreamExt;
933    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
934    /// process
935    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
936    ///     .into_keyed()
937    ///     .prefix_key(q!(|&(k, _)| k % 2))
938    /// #   .entries()
939    /// # }, |mut stream| async move {
940    /// // { (1, 1): [2, 3], (0, 2): [4] }
941    /// # let mut results = Vec::new();
942    /// # for _ in 0..3 {
943    /// #     results.push(stream.next().await.unwrap());
944    /// # }
945    /// # results.sort();
946    /// # assert_eq!(results, vec![((0, 2), 4), ((1, 1), 2), ((1, 1), 3)]);
947    /// # }));
948    /// # }
949    /// ```
950    pub fn prefix_key<K2, F>(
951        self,
952        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
953    ) -> KeyedStream<(K2, K), V, L, B, O, R>
954    where
955        F: Fn(&(K, V)) -> K2 + 'a,
956    {
957        let f: ManualExpr<F, _> =
958            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_borrow_ctx(ctx));
959        let map_f = q!({
960            let orig = f;
961            move |kv| {
962                let out = orig(&kv);
963                ((out, kv.0), kv.1)
964            }
965        })
966        .splice_fn1_ctx::<(K, V), ((K2, K), V)>(&OperatorContext::<L, B>::new(&self.location))
967        .into();
968
969        KeyedStream::new(
970            self.location.clone(),
971            HydroNode::Map {
972                f: map_f,
973                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
974                metadata: self
975                    .location
976                    .new_node_metadata(KeyedStream::<(K2, K), V, L, B, O, R>::collection_kind()),
977            },
978        )
979    }
980
981    /// Creates a stream containing only the elements of each group stream that satisfy a predicate
982    /// `f`, preserving the order of the elements within the group.
983    ///
984    /// The closure `f` receives a reference `&V` rather than an owned value `v` because filtering does
985    /// not modify or take ownership of the values. If you need to modify the values while filtering
986    /// use [`KeyedStream::filter_map`] instead.
987    ///
988    /// # Example
989    /// ```rust
990    /// # #[cfg(feature = "deploy")] {
991    /// # use hydro_lang::prelude::*;
992    /// # use futures::StreamExt;
993    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
994    /// process
995    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
996    ///     .into_keyed()
997    ///     .filter(q!(|&x| x > 2))
998    /// #   .entries()
999    /// # }, |mut stream| async move {
1000    /// // { 1: [3], 2: [4] }
1001    /// # let mut results = Vec::new();
1002    /// # for _ in 0..2 {
1003    /// #     results.push(stream.next().await.unwrap());
1004    /// # }
1005    /// # results.sort();
1006    /// # assert_eq!(results, vec![(1, 3), (2, 4)]);
1007    /// # }));
1008    /// # }
1009    /// ```
1010    pub fn filter<F>(
1011        self,
1012        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1013    ) -> KeyedStream<K, V, L, B, O, R>
1014    where
1015        F: Fn(&V) -> bool + 'a,
1016    {
1017        let f: ManualExpr<F, _> =
1018            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_borrow_ctx(ctx));
1019        let filter_f = q!({
1020            let orig = f;
1021            move |t: &(_, _)| orig(&t.1)
1022        })
1023        .splice_fn1_borrow_ctx::<(K, V), bool>(&OperatorContext::<L, B>::new(&self.location))
1024        .into();
1025
1026        KeyedStream::new(
1027            self.location.clone(),
1028            HydroNode::Filter {
1029                f: filter_f,
1030                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1031                metadata: self.location.new_node_metadata(Self::collection_kind()),
1032            },
1033        )
1034    }
1035
1036    /// Creates a stream containing only the elements of each group stream that satisfy a predicate
1037    /// `f` (which receives the key-value tuple), preserving the order of the elements within the group.
1038    ///
1039    /// The closure `f` receives a reference `&(K, V)` rather than an owned value `(K, V)` because filtering does
1040    /// not modify or take ownership of the values. If you need to modify the values while filtering
1041    /// use [`KeyedStream::filter_map_with_key`] instead.
1042    ///
1043    /// # Example
1044    /// ```rust
1045    /// # #[cfg(feature = "deploy")] {
1046    /// # use hydro_lang::prelude::*;
1047    /// # use futures::StreamExt;
1048    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1049    /// process
1050    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
1051    ///     .into_keyed()
1052    ///     .filter_with_key(q!(|&(k, v)| v - k == 2))
1053    /// #   .entries()
1054    /// # }, |mut stream| async move {
1055    /// // { 1: [3], 2: [4] }
1056    /// # let mut results = Vec::new();
1057    /// # for _ in 0..2 {
1058    /// #     results.push(stream.next().await.unwrap());
1059    /// # }
1060    /// # results.sort();
1061    /// # assert_eq!(results, vec![(1, 3), (2, 4)]);
1062    /// # }));
1063    /// # }
1064    /// ```
1065    pub fn filter_with_key<F>(
1066        self,
1067        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1068    ) -> KeyedStream<K, V, L, B, O, R>
1069    where
1070        F: Fn(&(K, V)) -> bool + 'a,
1071    {
1072        let filter_f = f
1073            .splice_fn1_borrow_ctx::<(K, V), bool>(&OperatorContext::<L, B>::new(&self.location))
1074            .into();
1075
1076        KeyedStream::new(
1077            self.location.clone(),
1078            HydroNode::Filter {
1079                f: filter_f,
1080                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1081                metadata: self.location.new_node_metadata(Self::collection_kind()),
1082            },
1083        )
1084    }
1085
1086    /// An operator that both filters and maps each value, with keys staying the same.
1087    /// It yields only the items for which the supplied closure `f` returns `Some(value)`.
1088    /// If you need access to the key, see [`KeyedStream::filter_map_with_key`].
1089    ///
1090    /// # Example
1091    /// ```rust
1092    /// # #[cfg(feature = "deploy")] {
1093    /// # use hydro_lang::prelude::*;
1094    /// # use futures::StreamExt;
1095    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1096    /// process
1097    ///     .source_iter(q!(vec![(1, "2"), (1, "hello"), (2, "4")]))
1098    ///     .into_keyed()
1099    ///     .filter_map(q!(|s| s.parse::<usize>().ok()))
1100    /// #   .entries()
1101    /// # }, |mut stream| async move {
1102    /// // { 1: [2], 2: [4] }
1103    /// # let mut results = Vec::new();
1104    /// # for _ in 0..2 {
1105    /// #     results.push(stream.next().await.unwrap());
1106    /// # }
1107    /// # results.sort();
1108    /// # assert_eq!(results, vec![(1, 2), (2, 4)]);
1109    /// # }));
1110    /// # }
1111    /// ```
1112    pub fn filter_map<U, F>(
1113        self,
1114        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1115    ) -> KeyedStream<K, U, L, B, O, R>
1116    where
1117        F: Fn(V) -> Option<U> + 'a,
1118    {
1119        let f: ManualExpr<F, _> =
1120            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_ctx(ctx));
1121        let filter_map_f = q!({
1122            let orig = f;
1123            move |(k, v)| orig(v).map(|o| (k, o))
1124        })
1125        .splice_fn1_ctx::<(K, V), Option<(K, U)>>(&OperatorContext::<L, B>::new(&self.location))
1126        .into();
1127
1128        KeyedStream::new(
1129            self.location.clone(),
1130            HydroNode::FilterMap {
1131                f: filter_map_f,
1132                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1133                metadata: self
1134                    .location
1135                    .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
1136            },
1137        )
1138    }
1139
1140    /// An operator that both filters and maps each key-value pair. The resulting values are **not**
1141    /// re-grouped even they are tuples; instead they will be grouped under the original key.
1142    /// It yields only the items for which the supplied closure `f` returns `Some(value)`.
1143    ///
1144    /// # Example
1145    /// ```rust
1146    /// # #[cfg(feature = "deploy")] {
1147    /// # use hydro_lang::prelude::*;
1148    /// # use futures::StreamExt;
1149    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1150    /// process
1151    ///     .source_iter(q!(vec![(1, "2"), (1, "hello"), (2, "2")]))
1152    ///     .into_keyed()
1153    ///     .filter_map_with_key(q!(|(k, s)| s.parse::<usize>().ok().filter(|v| v == &k)))
1154    /// #   .entries()
1155    /// # }, |mut stream| async move {
1156    /// // { 2: [2] }
1157    /// # let mut results = Vec::new();
1158    /// # for _ in 0..1 {
1159    /// #     results.push(stream.next().await.unwrap());
1160    /// # }
1161    /// # results.sort();
1162    /// # assert_eq!(results, vec![(2, 2)]);
1163    /// # }));
1164    /// # }
1165    /// ```
1166    pub fn filter_map_with_key<U, F>(
1167        self,
1168        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1169    ) -> KeyedStream<K, U, L, B, O, R>
1170    where
1171        F: Fn((K, V)) -> Option<U> + 'a,
1172        K: Clone,
1173    {
1174        let f: ManualExpr<F, _> =
1175            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_ctx(ctx));
1176        let filter_map_f = q!({
1177            let orig = f;
1178            move |(k, v)| {
1179                let out = orig((Clone::clone(&k), v));
1180                out.map(|o| (k, o))
1181            }
1182        })
1183        .splice_fn1_ctx::<(K, V), Option<(K, U)>>(&OperatorContext::<L, B>::new(&self.location))
1184        .into();
1185
1186        KeyedStream::new(
1187            self.location.clone(),
1188            HydroNode::FilterMap {
1189                f: filter_map_f,
1190                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1191                metadata: self
1192                    .location
1193                    .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
1194            },
1195        )
1196    }
1197
1198    /// Generates a keyed stream that maps each value `v` to a tuple `(v, x)`,
1199    /// where `v` is the value of `other`, a bounded [`super::singleton::Singleton`] or
1200    /// [`Optional`]. If `other` is an empty [`Optional`], no values will be produced.
1201    ///
1202    /// # Example
1203    /// ```rust
1204    /// # #[cfg(feature = "deploy")] {
1205    /// # use hydro_lang::prelude::*;
1206    /// # use futures::StreamExt;
1207    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1208    /// let tick = process.tick();
1209    /// let batch = process
1210    ///   .source_iter(q!(vec![(1, 123), (1, 456), (2, 123)]))
1211    ///   .into_keyed()
1212    ///   .batch(&tick, nondet!(/** test */));
1213    /// let count = batch.clone().entries().count(); // `count()` returns a singleton
1214    /// batch.cross_singleton(count).all_ticks().entries()
1215    /// # }, |mut stream| async move {
1216    /// // { 1: [(123, 3), (456, 3)], 2: [(123, 3)] }
1217    /// # let mut results = Vec::new();
1218    /// # for _ in 0..3 {
1219    /// #     results.push(stream.next().await.unwrap());
1220    /// # }
1221    /// # results.sort();
1222    /// # assert_eq!(results, vec![(1, (123, 3)), (1, (456, 3)), (2, (123, 3))]);
1223    /// # }));
1224    /// # }
1225    /// ```
1226    pub fn cross_singleton<O2>(
1227        self,
1228        other: impl Into<Optional<O2, L, Bounded>>,
1229    ) -> KeyedStream<K, (V, O2), L, B, O, R>
1230    where
1231        O2: Clone,
1232    {
1233        let other: Optional<O2, L, Bounded> = other.into();
1234        check_matching_location(&self.location, &other.location);
1235
1236        Stream::<((K, V), O2), L, B, O, R>::new(
1237            self.location.clone(),
1238            HydroNode::CrossSingleton {
1239                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1240                right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
1241                metadata: self
1242                    .location
1243                    .new_node_metadata(Stream::<((K, V), O2), L, B, O, R>::collection_kind()),
1244            },
1245        )
1246        .map(q!(|((k, v), o2)| (k, (v, o2))))
1247        .into_keyed()
1248    }
1249
1250    /// For each value `v` in each group, transform `v` using `f` and then treat the
1251    /// result as an [`Iterator`] to produce values one by one within the same group.
1252    /// The implementation for [`Iterator`] for the output type `I` must produce items
1253    /// in a **deterministic** order.
1254    ///
1255    /// For example, `I` could be a `Vec`, but not a `HashSet`. If the order of the items in `I` is
1256    /// not deterministic, use [`KeyedStream::flat_map_unordered`] instead.
1257    ///
1258    /// # Example
1259    /// ```rust
1260    /// # #[cfg(feature = "deploy")] {
1261    /// # use hydro_lang::prelude::*;
1262    /// # use futures::StreamExt;
1263    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1264    /// process
1265    ///     .source_iter(q!(vec![(1, vec![2, 3]), (1, vec![4]), (2, vec![5, 6])]))
1266    ///     .into_keyed()
1267    ///     .flat_map_ordered(q!(|x| x))
1268    /// #   .entries()
1269    /// # }, |mut stream| async move {
1270    /// // { 1: [2, 3, 4], 2: [5, 6] }
1271    /// # let mut results = Vec::new();
1272    /// # for _ in 0..5 {
1273    /// #     results.push(stream.next().await.unwrap());
1274    /// # }
1275    /// # results.sort();
1276    /// # assert_eq!(results, vec![(1, 2), (1, 3), (1, 4), (2, 5), (2, 6)]);
1277    /// # }));
1278    /// # }
1279    /// ```
1280    pub fn flat_map_ordered<U, I, F>(
1281        self,
1282        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1283    ) -> KeyedStream<K, U, L, B, O, R>
1284    where
1285        I: IntoIterator<Item = U>,
1286        F: Fn(V) -> I + 'a,
1287        K: Clone,
1288    {
1289        let f: ManualExpr<F, _> =
1290            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_ctx(ctx));
1291        let flat_map_f = q!({
1292            let orig = f;
1293            move |(k, v)| orig(v).into_iter().map(move |u| (Clone::clone(&k), u))
1294        })
1295        .splice_fn1_ctx::<(K, V), _>(&OperatorContext::<L, B>::new(&self.location))
1296        .into();
1297
1298        KeyedStream::new(
1299            self.location.clone(),
1300            HydroNode::FlatMap {
1301                f: flat_map_f,
1302                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1303                metadata: self
1304                    .location
1305                    .new_node_metadata(KeyedStream::<K, U, L, B, O, R>::collection_kind()),
1306            },
1307        )
1308    }
1309
1310    /// Like [`KeyedStream::flat_map_ordered`], but allows the implementation of [`Iterator`]
1311    /// for the output type `I` to produce items in any order.
1312    ///
1313    /// # Example
1314    /// ```rust
1315    /// # #[cfg(feature = "deploy")] {
1316    /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
1317    /// # use futures::StreamExt;
1318    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
1319    /// process
1320    ///     .source_iter(q!(vec![
1321    ///         (1, std::collections::HashSet::<i32>::from_iter(vec![2, 3])),
1322    ///         (2, std::collections::HashSet::from_iter(vec![4, 5]))
1323    ///     ]))
1324    ///     .into_keyed()
1325    ///     .flat_map_unordered(q!(|x| x))
1326    /// #   .entries()
1327    /// # }, |mut stream| async move {
1328    /// // { 1: [2, 3], 2: [4, 5] } with values in each group in unknown order
1329    /// # let mut results = Vec::new();
1330    /// # for _ in 0..4 {
1331    /// #     results.push(stream.next().await.unwrap());
1332    /// # }
1333    /// # results.sort();
1334    /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4), (2, 5)]);
1335    /// # }));
1336    /// # }
1337    /// ```
1338    pub fn flat_map_unordered<U, I, F>(
1339        self,
1340        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1341    ) -> KeyedStream<K, U, L, B, NoOrder, R>
1342    where
1343        I: IntoIterator<Item = U>,
1344        F: Fn(V) -> I + 'a,
1345        K: Clone,
1346    {
1347        let f: ManualExpr<F, _> =
1348            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_ctx(ctx));
1349        let flat_map_f = q!({
1350            let orig = f;
1351            move |(k, v)| orig(v).into_iter().map(move |u| (Clone::clone(&k), u))
1352        })
1353        .splice_fn1_ctx::<(K, V), _>(&OperatorContext::<L, B>::new(&self.location))
1354        .into();
1355
1356        KeyedStream::new(
1357            self.location.clone(),
1358            HydroNode::FlatMap {
1359                f: flat_map_f,
1360                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1361                metadata: self
1362                    .location
1363                    .new_node_metadata(KeyedStream::<K, U, L, B, NoOrder, R>::collection_kind()),
1364            },
1365        )
1366    }
1367
1368    /// For each value `v` in each group, treat `v` as an [`Iterator`] and produce its items one by one
1369    /// within the same group. The implementation for [`Iterator`] for the value type `V` must produce
1370    /// items in a **deterministic** order.
1371    ///
1372    /// For example, `V` could be a `Vec`, but not a `HashSet`. If the order of the items in `V` is
1373    /// not deterministic, use [`KeyedStream::flatten_unordered`] instead.
1374    ///
1375    /// # Example
1376    /// ```rust
1377    /// # #[cfg(feature = "deploy")] {
1378    /// # use hydro_lang::prelude::*;
1379    /// # use futures::StreamExt;
1380    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1381    /// process
1382    ///     .source_iter(q!(vec![(1, vec![2, 3]), (1, vec![4]), (2, vec![5, 6])]))
1383    ///     .into_keyed()
1384    ///     .flatten_ordered()
1385    /// #   .entries()
1386    /// # }, |mut stream| async move {
1387    /// // { 1: [2, 3, 4], 2: [5, 6] }
1388    /// # let mut results = Vec::new();
1389    /// # for _ in 0..5 {
1390    /// #     results.push(stream.next().await.unwrap());
1391    /// # }
1392    /// # results.sort();
1393    /// # assert_eq!(results, vec![(1, 2), (1, 3), (1, 4), (2, 5), (2, 6)]);
1394    /// # }));
1395    /// # }
1396    /// ```
1397    pub fn flatten_ordered<U>(self) -> KeyedStream<K, U, L, B, O, R>
1398    where
1399        V: IntoIterator<Item = U>,
1400        K: Clone,
1401    {
1402        self.flat_map_ordered(q!(|d| d))
1403    }
1404
1405    /// Like [`KeyedStream::flatten_ordered`], but allows the implementation of [`Iterator`]
1406    /// for the value type `V` to produce items in any order.
1407    ///
1408    /// # Example
1409    /// ```rust
1410    /// # #[cfg(feature = "deploy")] {
1411    /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
1412    /// # use futures::StreamExt;
1413    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
1414    /// process
1415    ///     .source_iter(q!(vec![
1416    ///         (1, std::collections::HashSet::<i32>::from_iter(vec![2, 3])),
1417    ///         (2, std::collections::HashSet::from_iter(vec![4, 5]))
1418    ///     ]))
1419    ///     .into_keyed()
1420    ///     .flatten_unordered()
1421    /// #   .entries()
1422    /// # }, |mut stream| async move {
1423    /// // { 1: [2, 3], 2: [4, 5] } with values in each group in unknown order
1424    /// # let mut results = Vec::new();
1425    /// # for _ in 0..4 {
1426    /// #     results.push(stream.next().await.unwrap());
1427    /// # }
1428    /// # results.sort();
1429    /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4), (2, 5)]);
1430    /// # }));
1431    /// # }
1432    /// ```
1433    pub fn flatten_unordered<U>(self) -> KeyedStream<K, U, L, B, NoOrder, R>
1434    where
1435        V: IntoIterator<Item = U>,
1436        K: Clone,
1437    {
1438        self.flat_map_unordered(q!(|d| d))
1439    }
1440
1441    /// An operator which allows you to "inspect" each element of a stream without
1442    /// modifying it. The closure `f` is called on a reference to each value. This is
1443    /// mainly useful for debugging, and should not be used to generate side-effects.
1444    ///
1445    /// # Example
1446    /// ```rust
1447    /// # #[cfg(feature = "deploy")] {
1448    /// # use hydro_lang::prelude::*;
1449    /// # use futures::StreamExt;
1450    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1451    /// process
1452    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
1453    ///     .into_keyed()
1454    ///     .inspect(q!(|v| println!("{}", v)))
1455    /// #   .entries()
1456    /// # }, |mut stream| async move {
1457    /// # let mut results = Vec::new();
1458    /// # for _ in 0..3 {
1459    /// #     results.push(stream.next().await.unwrap());
1460    /// # }
1461    /// # results.sort();
1462    /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4)]);
1463    /// # }));
1464    /// # }
1465    /// ```
1466    pub fn inspect<F>(self, f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy) -> Self
1467    where
1468        F: Fn(&V) + 'a,
1469    {
1470        let f: ManualExpr<F, _> =
1471            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn1_borrow_ctx(ctx));
1472        let inspect_f = q!({
1473            let orig = f;
1474            move |t: &(_, _)| orig(&t.1)
1475        })
1476        .splice_fn1_borrow_ctx::<(K, V), ()>(&OperatorContext::<L, B>::new(&self.location))
1477        .into();
1478
1479        KeyedStream::new(
1480            self.location.clone(),
1481            HydroNode::Inspect {
1482                f: inspect_f,
1483                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1484                metadata: self.location.new_node_metadata(Self::collection_kind()),
1485            },
1486        )
1487    }
1488
1489    /// An operator which allows you to "inspect" each element of a stream without
1490    /// modifying it. The closure `f` is called on a reference to each key-value pair. This is
1491    /// mainly useful for debugging, and should not be used to generate side-effects.
1492    ///
1493    /// # Example
1494    /// ```rust
1495    /// # #[cfg(feature = "deploy")] {
1496    /// # use hydro_lang::prelude::*;
1497    /// # use futures::StreamExt;
1498    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1499    /// process
1500    ///     .source_iter(q!(vec![(1, 2), (1, 3), (2, 4)]))
1501    ///     .into_keyed()
1502    ///     .inspect_with_key(q!(|(k, v)| println!("{}: {}", k, v)))
1503    /// #   .entries()
1504    /// # }, |mut stream| async move {
1505    /// # let mut results = Vec::new();
1506    /// # for _ in 0..3 {
1507    /// #     results.push(stream.next().await.unwrap());
1508    /// # }
1509    /// # results.sort();
1510    /// # assert_eq!(results, vec![(1, 2), (1, 3), (2, 4)]);
1511    /// # }));
1512    /// # }
1513    /// ```
1514    pub fn inspect_with_key<F>(self, f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>>) -> Self
1515    where
1516        F: Fn(&(K, V)) + 'a,
1517    {
1518        let inspect_f = f
1519            .splice_fn1_borrow_ctx::<(K, V), ()>(&OperatorContext::<L, B>::new(&self.location))
1520            .into();
1521
1522        KeyedStream::new(
1523            self.location.clone(),
1524            HydroNode::Inspect {
1525                f: inspect_f,
1526                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1527                metadata: self.location.new_node_metadata(Self::collection_kind()),
1528            },
1529        )
1530    }
1531
1532    /// An operator which allows you to "name" a `HydroNode`.
1533    /// This is only used for testing, to correlate certain `HydroNode`s with IDs.
1534    pub fn ir_node_named(self, name: &str) -> KeyedStream<K, V, L, B, O, R> {
1535        {
1536            let mut node = self.ir_node.borrow_mut();
1537            let metadata = node.metadata_mut();
1538            metadata.tag = Some(name.to_owned());
1539        }
1540        self
1541    }
1542
1543    /// A special case of [`Stream::scan`] for keyed streams. For each key group the values are transformed via the `f` combinator.
1544    ///
1545    /// Unlike [`KeyedStream::fold`] which only returns the final accumulated value, `scan` produces a new stream
1546    /// containing all intermediate accumulated values paired with the key. The scan operation can also terminate
1547    /// early by returning `None`.
1548    ///
1549    /// The function takes a mutable reference to the accumulator and the current element, and returns
1550    /// an `Option<U>`. If the function returns `Some(value)`, `value` is emitted to the output stream.
1551    /// If the function returns `None`, the stream is terminated and no more elements are processed.
1552    ///
1553    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1554    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
1555    /// referenced collection has the same location and boundedness as this stream.
1556    ///
1557    /// # Example
1558    /// ```rust
1559    /// # #[cfg(feature = "deploy")] {
1560    /// # use hydro_lang::prelude::*;
1561    /// # use futures::StreamExt;
1562    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1563    /// process
1564    ///     .source_iter(q!(vec![(0, 1), (0, 3), (1, 3), (1, 4)]))
1565    ///     .into_keyed()
1566    ///     .scan(
1567    ///         q!(|| 0),
1568    ///         q!(|acc, x| {
1569    ///             *acc += x;
1570    ///             if *acc % 2 == 0 { None } else { Some(*acc) }
1571    ///         }),
1572    ///     )
1573    /// #   .entries()
1574    /// # }, |mut stream| async move {
1575    /// // Output: { 0: [1], 1: [3, 7] }
1576    /// # let mut results = Vec::new();
1577    /// # for _ in 0..3 {
1578    /// #     results.push(stream.next().await.unwrap());
1579    /// # }
1580    /// # results.sort();
1581    /// # assert_eq!(results, vec![(0, 1), (1, 3), (1, 7)]);
1582    /// # }));
1583    /// # }
1584    /// ```
1585    pub fn scan<A, U, I, F>(
1586        self,
1587        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>> + Copy,
1588        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1589    ) -> KeyedStream<K, U, L, B, TotalOrder, ExactlyOnce>
1590    where
1591        O: IsOrdered,
1592        R: IsExactlyOnce,
1593        K: Clone + Eq + Hash,
1594        I: Fn() -> A + 'a,
1595        F: Fn(&mut A, V) -> Option<U> + 'a,
1596    {
1597        let f: ManualExpr<F, _> =
1598            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn2_borrow_mut_ctx(ctx));
1599        self.make_totally_ordered().make_exactly_once().generator(
1600            init,
1601            q!({
1602                let orig = f;
1603                move |state, v| {
1604                    if let Some(out) = orig(state, v) {
1605                        Generate::Yield(out)
1606                    } else {
1607                        Generate::Break
1608                    }
1609                }
1610            }),
1611        )
1612    }
1613
1614    /// Iteratively processes the elements in each group using a state machine that can yield
1615    /// elements as it processes its inputs. This is designed to mirror the unstable generator
1616    /// syntax in Rust, without requiring special syntax.
1617    ///
1618    /// Like [`KeyedStream::scan`], this function takes in an initializer that emits the initial
1619    /// state for each group. The second argument defines the processing logic, taking in a
1620    /// mutable reference to the group's state and the value to be processed. It emits a
1621    /// [`Generate`] value, whose variants define what is emitted and whether further inputs
1622    /// should be processed.
1623    ///
1624    /// The `init` and `f` closures may capture bounded singletons, optionals, or streams by
1625    /// reference via [`by_ref()`](crate::live_collections::Singleton::by_ref), as long as the
1626    /// referenced collection has the same location and boundedness as this stream.
1627    ///
1628    /// # Example
1629    /// ```rust
1630    /// # #[cfg(feature = "deploy")] {
1631    /// # use hydro_lang::prelude::*;
1632    /// # use futures::StreamExt;
1633    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1634    /// process
1635    ///     .source_iter(q!(vec![(0, 1), (0, 3), (0, 100), (0, 10), (1, 3), (1, 4), (1, 3)]))
1636    ///     .into_keyed()
1637    ///     .generator(
1638    ///         q!(|| 0),
1639    ///         q!(|acc, x| {
1640    ///             *acc += x;
1641    ///             if *acc > 100 {
1642    ///                 hydro_lang::live_collections::keyed_stream::Generate::Return(
1643    ///                     "done!".to_owned()
1644    ///                 )
1645    ///             } else if *acc % 2 == 0 {
1646    ///                 hydro_lang::live_collections::keyed_stream::Generate::Yield(
1647    ///                     "even".to_owned()
1648    ///                 )
1649    ///             } else {
1650    ///                 hydro_lang::live_collections::keyed_stream::Generate::Continue
1651    ///             }
1652    ///         }),
1653    ///     )
1654    /// #   .entries()
1655    /// # }, |mut stream| async move {
1656    /// // Output: { 0: ["even", "done!"], 1: ["even"] }
1657    /// # let mut results = Vec::new();
1658    /// # for _ in 0..3 {
1659    /// #     results.push(stream.next().await.unwrap());
1660    /// # }
1661    /// # results.sort();
1662    /// # assert_eq!(results, vec![(0, "done!".to_owned()), (0, "even".to_owned()), (1, "even".to_owned())]);
1663    /// # }));
1664    /// # }
1665    /// ```
1666    pub fn generator<A, U, I, F>(
1667        self,
1668        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>> + Copy,
1669        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1670    ) -> KeyedStream<K, U, L, B, TotalOrder, ExactlyOnce>
1671    where
1672        O: IsOrdered,
1673        R: IsExactlyOnce,
1674        K: Clone + Eq + Hash,
1675        I: Fn() -> A + 'a,
1676        F: Fn(&mut A, V) -> Generate<U> + 'a,
1677    {
1678        let init: ManualExpr<I, _> =
1679            ManualExpr::new(move |ctx: &OperatorContext<L, B>| init.splice_fn0_ctx(ctx));
1680        let f: ManualExpr<F, _> =
1681            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn2_borrow_mut_ctx(ctx));
1682
1683        let this = self.make_totally_ordered().make_exactly_once();
1684
1685        let scan_init = crate::handoff_ref::with_ref_capture(|| {
1686            q!(|| HashMap::new())
1687                .splice_fn0_ctx::<HashMap<K, Option<A>>>(&OperatorContext::<L, B>::new(
1688                    &this.location,
1689                ))
1690                .into()
1691        });
1692        let scan_f = crate::handoff_ref::with_ref_capture(|| {
1693            q!(move |acc: &mut HashMap<_, _>, (k, v)| {
1694                let existing_state = acc.entry(Clone::clone(&k)).or_insert_with(|| Some(init()));
1695                if let Some(existing_state_value) = existing_state {
1696                    match f(existing_state_value, v) {
1697                        Generate::Yield(out) => Some(Some((k, out))),
1698                        Generate::Return(out) => {
1699                            let _ = existing_state.take(); // TODO(shadaj): garbage collect with termination markers
1700                            Some(Some((k, out)))
1701                        }
1702                        Generate::Break => {
1703                            let _ = existing_state.take(); // TODO(shadaj): garbage collect with termination markers
1704                            Some(None)
1705                        }
1706                        Generate::Continue => Some(None),
1707                    }
1708                } else {
1709                    Some(None)
1710                }
1711            })
1712            .splice_fn2_borrow_mut_ctx::<HashMap<K, Option<A>>, (K, V), _>(
1713                &OperatorContext::<L, B>::new(&this.location),
1714            )
1715            .into()
1716        });
1717
1718        let scan_node = HydroNode::Scan {
1719            init: scan_init,
1720            acc: scan_f,
1721            input: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
1722            metadata: this.location.new_node_metadata(Stream::<
1723                Option<(K, U)>,
1724                L,
1725                B,
1726                TotalOrder,
1727                ExactlyOnce,
1728            >::collection_kind()),
1729        };
1730
1731        let flatten_f = q!(|d| d)
1732            .splice_fn1_ctx::<Option<(K, U)>, _>(&OperatorContext::<L, B>::new(&this.location))
1733            .into();
1734        let flatten_node = HydroNode::FlatMap {
1735            f: flatten_f,
1736            input: Box::new(scan_node),
1737            metadata: this.location.new_node_metadata(KeyedStream::<
1738                K,
1739                U,
1740                L,
1741                B,
1742                TotalOrder,
1743                ExactlyOnce,
1744            >::collection_kind()),
1745        };
1746
1747        KeyedStream::new(this.location.clone(), flatten_node)
1748    }
1749
1750    /// A variant of [`Stream::fold`], intended for keyed streams. The aggregation is executed
1751    /// in-order across the values in each group. But the aggregation function returns a boolean,
1752    /// which when true indicates that the aggregated result is complete and can be released to
1753    /// downstream computation. Unlike [`KeyedStream::fold`], this means that even if the input
1754    /// stream is [`super::boundedness::Unbounded`], the outputs of the fold can be processed like
1755    /// normal stream elements.
1756    ///
1757    /// # Example
1758    /// ```rust
1759    /// # #[cfg(feature = "deploy")] {
1760    /// # use hydro_lang::prelude::*;
1761    /// # use futures::StreamExt;
1762    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1763    /// process
1764    ///     .source_iter(q!(vec![(0, 2), (0, 3), (1, 3), (1, 6)]))
1765    ///     .into_keyed()
1766    ///     .fold_early_stop(
1767    ///         q!(|| 0),
1768    ///         q!(|acc, x| {
1769    ///             *acc += x;
1770    ///             x % 2 == 0
1771    ///         }),
1772    ///     )
1773    /// #   .entries()
1774    /// # }, |mut stream| async move {
1775    /// // Output: { 0: 2, 1: 9 }
1776    /// # let mut results = Vec::new();
1777    /// # for _ in 0..2 {
1778    /// #     results.push(stream.next().await.unwrap());
1779    /// # }
1780    /// # results.sort();
1781    /// # assert_eq!(results, vec![(0, 2), (1, 9)]);
1782    /// # }));
1783    /// # }
1784    /// ```
1785    pub fn fold_early_stop<A, I, F>(
1786        self,
1787        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>> + Copy,
1788        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B>> + Copy,
1789    ) -> KeyedSingleton<K, A, L, B::WithBoundedValue>
1790    where
1791        O: IsOrdered,
1792        R: IsExactlyOnce,
1793        K: Clone + Eq + Hash,
1794        I: Fn() -> A + 'a,
1795        F: Fn(&mut A, V) -> bool + 'a,
1796    {
1797        let init: ManualExpr<I, _> =
1798            ManualExpr::new(move |ctx: &OperatorContext<L, B>| init.splice_fn0_ctx(ctx));
1799        let f: ManualExpr<F, _> =
1800            ManualExpr::new(move |ctx: &OperatorContext<L, B>| f.splice_fn2_borrow_mut_ctx(ctx));
1801        let out_without_bound_cast = self.generator(
1802            q!(move || Some(init())),
1803            q!(move |key_state, v| {
1804                if let Some(key_state_value) = key_state.as_mut() {
1805                    if f(key_state_value, v) {
1806                        Generate::Return(key_state.take().unwrap())
1807                    } else {
1808                        Generate::Continue
1809                    }
1810                } else {
1811                    unreachable!()
1812                }
1813            }),
1814        );
1815
1816        // SAFETY: The generator will only ever return at most one value per key, since once it
1817        // returns a value for a key it will never process any more values for that key.
1818        out_without_bound_cast.cast_at_most_one_entry_per_key()
1819    }
1820
1821    /// Gets the first element inside each group of values as a [`KeyedSingleton`] that preserves
1822    /// the original group keys. Requires the input stream to have [`TotalOrder`] guarantees,
1823    /// otherwise the first element would be non-deterministic.
1824    ///
1825    /// # Example
1826    /// ```rust
1827    /// # #[cfg(feature = "deploy")] {
1828    /// # use hydro_lang::prelude::*;
1829    /// # use futures::StreamExt;
1830    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1831    /// process
1832    ///     .source_iter(q!(vec![(0, 2), (0, 3), (1, 3), (1, 6)]))
1833    ///     .into_keyed()
1834    ///     .first()
1835    /// #   .entries()
1836    /// # }, |mut stream| async move {
1837    /// // Output: { 0: 2, 1: 3 }
1838    /// # let mut results = Vec::new();
1839    /// # for _ in 0..2 {
1840    /// #     results.push(stream.next().await.unwrap());
1841    /// # }
1842    /// # results.sort();
1843    /// # assert_eq!(results, vec![(0, 2), (1, 3)]);
1844    /// # }));
1845    /// # }
1846    /// ```
1847    pub fn first(self) -> KeyedSingleton<K, V, L, B::WithBoundedValue>
1848    where
1849        O: IsOrdered,
1850        R: IsExactlyOnce,
1851        K: Clone + Eq + Hash,
1852    {
1853        self.fold_early_stop(
1854            q!(|| None),
1855            q!(|acc, v| {
1856                *acc = Some(v);
1857                true
1858            }),
1859        )
1860        .map(q!(|v| v.unwrap()))
1861    }
1862
1863    /// Returns a keyed stream containing at most the first `n` values per key,
1864    /// preserving the original order within each group. Similar to SQL `LIMIT`
1865    /// applied per group.
1866    ///
1867    /// This requires the stream to have a [`TotalOrder`] guarantee and [`ExactlyOnce`]
1868    /// retries, since the result depends on the order and cardinality of elements
1869    /// within each group.
1870    ///
1871    /// # Example
1872    /// ```rust
1873    /// # #[cfg(feature = "deploy")] {
1874    /// # use hydro_lang::prelude::*;
1875    /// # use futures::StreamExt;
1876    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1877    /// process
1878    ///     .source_iter(q!(vec![(1, 10), (1, 20), (1, 30), (2, 40), (2, 50)]))
1879    ///     .into_keyed()
1880    ///     .limit(q!(2))
1881    /// #   .entries()
1882    /// # }, |mut stream| async move {
1883    /// // { 1: [10, 20], 2: [40, 50] }
1884    /// # let mut results = Vec::new();
1885    /// # for _ in 0..4 {
1886    /// #     results.push(stream.next().await.unwrap());
1887    /// # }
1888    /// # results.sort();
1889    /// # assert_eq!(results, vec![(1, 10), (1, 20), (2, 40), (2, 50)]);
1890    /// # }));
1891    /// # }
1892    /// ```
1893    pub fn limit(
1894        self,
1895        n: impl QuotedWithContext<'a, usize, OperatorContext<L, B>> + Copy + 'a,
1896    ) -> KeyedStream<K, V, L, B, TotalOrder, ExactlyOnce>
1897    where
1898        O: IsOrdered,
1899        R: IsExactlyOnce,
1900        K: Clone + Eq + Hash,
1901    {
1902        self.generator(
1903            q!(|| 0usize),
1904            q!(move |count, item| {
1905                if *count == n {
1906                    Generate::Break
1907                } else {
1908                    *count += 1;
1909                    if *count == n {
1910                        Generate::Return(item)
1911                    } else {
1912                        Generate::Yield(item)
1913                    }
1914                }
1915            }),
1916        )
1917    }
1918
1919    /// Assigns a zero-based index to each value within each key group, emitting
1920    /// `(K, (index, V))` tuples with per-key sequential indices.
1921    ///
1922    /// The output keyed stream has [`TotalOrder`] and [`ExactlyOnce`] guarantees.
1923    /// This is a streaming operator that processes elements as they arrive.
1924    ///
1925    /// # Example
1926    /// ```rust
1927    /// # #[cfg(feature = "deploy")] {
1928    /// # use hydro_lang::prelude::*;
1929    /// # use futures::StreamExt;
1930    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1931    /// process
1932    ///     .source_iter(q!(vec![(1, 10), (2, 20), (1, 30)]))
1933    ///     .into_keyed()
1934    ///     .enumerate()
1935    /// # .entries()
1936    /// # }, |mut stream| async move {
1937    /// // per-key indices: { 1: [(0, 10), (1, 30)], 2: [(0, 20)] }
1938    /// # let mut results = Vec::new();
1939    /// # for _ in 0..3 {
1940    /// #     results.push(stream.next().await.unwrap());
1941    /// # }
1942    /// # let key1: Vec<_> = results.iter().filter(|(k, _)| *k == 1).map(|(_, v)| *v).collect();
1943    /// # let key2: Vec<_> = results.iter().filter(|(k, _)| *k == 2).map(|(_, v)| *v).collect();
1944    /// # assert_eq!(key1, vec![(0, 10), (1, 30)]);
1945    /// # assert_eq!(key2, vec![(0, 20)]);
1946    /// # }));
1947    /// # }
1948    /// ```
1949    pub fn enumerate(self) -> KeyedStream<K, (usize, V), L, B, TotalOrder, ExactlyOnce>
1950    where
1951        O: IsOrdered,
1952        R: IsExactlyOnce,
1953        K: Eq + Hash + Clone,
1954    {
1955        self.scan(
1956            q!(|| 0),
1957            q!(|acc, next| {
1958                let curr = *acc;
1959                *acc += 1;
1960                Some((curr, next))
1961            }),
1962        )
1963    }
1964
1965    /// Counts the number of elements in each group, producing a [`KeyedSingleton`] with the counts.
1966    ///
1967    /// # Example
1968    /// ```rust
1969    /// # #[cfg(feature = "deploy")] {
1970    /// # use hydro_lang::prelude::*;
1971    /// # use futures::StreamExt;
1972    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1973    /// let tick = process.tick();
1974    /// let numbers = process
1975    ///     .source_iter(q!(vec![(1, 2), (2, 3), (1, 3), (2, 4), (1, 5)]))
1976    ///     .into_keyed();
1977    /// let batch = numbers.batch(&tick, nondet!(/** test */));
1978    /// batch
1979    ///     .value_counts()
1980    ///     .entries()
1981    ///     .all_ticks()
1982    /// # }, |mut stream| async move {
1983    /// // (1, 3), (2, 2)
1984    /// # let mut results = Vec::new();
1985    /// # for _ in 0..2 {
1986    /// #     results.push(stream.next().await.unwrap());
1987    /// # }
1988    /// # results.sort();
1989    /// # assert_eq!(results, vec![(1, 3), (2, 2)]);
1990    /// # }));
1991    /// # }
1992    /// ```
1993    pub fn value_counts(
1994        self,
1995    ) -> KeyedSingleton<K, usize, L, <B as KeyedSingletonBound>::KeyedStreamToMonotone>
1996    where
1997        R: IsExactlyOnce,
1998        K: Eq + Hash,
1999    {
2000        self.make_exactly_once()
2001            .assume_ordering_trusted(
2002                nondet!(/** ordering within each group affects neither result nor intermediates */),
2003            )
2004            .fold(
2005                q!(|| 0),
2006                q!(
2007                    |acc, _| *acc += 1,
2008                    monotone = manual_proof!(/** += 1 is monotonic */)
2009                ),
2010            )
2011    }
2012
2013    /// Like [`Stream::fold`] but in the spirit of SQL `GROUP BY`, aggregates the values in each
2014    /// group via the `comb` closure.
2015    ///
2016    /// Depending on the input stream guarantees, the closure may need to be commutative
2017    /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
2018    ///
2019    /// If the input and output value types are the same and do not require initialization then use
2020    /// [`KeyedStream::reduce`].
2021    ///
2022    /// # Example
2023    /// ```rust
2024    /// # #[cfg(feature = "deploy")] {
2025    /// # use hydro_lang::prelude::*;
2026    /// # use futures::StreamExt;
2027    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2028    /// let tick = process.tick();
2029    /// let numbers = process
2030    ///     .source_iter(q!(vec![(1, false), (2, true), (1, false), (2, false)]))
2031    ///     .into_keyed();
2032    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2033    /// batch
2034    ///     .fold(q!(|| false), q!(|acc, x| *acc |= x))
2035    ///     .entries()
2036    ///     .all_ticks()
2037    /// # }, |mut stream| async move {
2038    /// // (1, false), (2, true)
2039    /// # let mut results = Vec::new();
2040    /// # for _ in 0..2 {
2041    /// #     results.push(stream.next().await.unwrap());
2042    /// # }
2043    /// # results.sort();
2044    /// # assert_eq!(results, vec![(1, false), (2, true)]);
2045    /// # }));
2046    /// # }
2047    /// ```
2048    pub fn fold<A, I: Fn() -> A + 'a, F: 'a + Fn(&mut A, V), C, Idemp, M, B2: KeyedSingletonBound>(
2049        self,
2050        init: impl IntoQuotedMut<'a, I, OperatorContext<L, B>>,
2051        comb: impl IntoQuotedMut<'a, F, OperatorContext<L, B>, AggFuncAlgebra<V, B, C, Idemp, M>>,
2052    ) -> KeyedSingleton<K, A, L, B2>
2053    where
2054        K: Eq + Hash,
2055        C: ValidCommutativityFor<O>,
2056        Idemp: ValidIdempotenceFor<R>,
2057        B: ApplyMonotoneKeyedStream<M, B2>,
2058    {
2059        let init = init
2060            .splice_fn0_ctx(&OperatorContext::<L, B>::new(&self.location))
2061            .into();
2062        let (comb, proof) =
2063            comb.splice_fn2_borrow_mut_ctx_props(&OperatorContext::<L, B>::new(&self.location));
2064        proof.register_proof(&comb);
2065
2066        let retried = self
2067            .assume_retries::<ExactlyOnce>(nondet!(/** the combinator function is idempotent */));
2068
2069        KeyedSingleton::new(
2070            retried.location.clone(),
2071            HydroNode::FoldKeyed {
2072                init,
2073                acc: comb.into(),
2074                input: Box::new(retried.ir_node.replace(HydroNode::Placeholder)),
2075                metadata: retried
2076                    .location
2077                    .new_node_metadata(KeyedSingleton::<K, A, L, B2>::collection_kind()),
2078            },
2079        )
2080        .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
2081    }
2082
2083    /// Like [`Stream::reduce`] but in the spirit of SQL `GROUP BY`, aggregates the values in each
2084    /// group via the `comb` closure.
2085    ///
2086    /// Depending on the input stream guarantees, the closure may need to be commutative
2087    /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
2088    ///
2089    /// If you need the accumulated value to have a different type than the input, use [`KeyedStream::fold`].
2090    ///
2091    /// # Example
2092    /// ```rust
2093    /// # #[cfg(feature = "deploy")] {
2094    /// # use hydro_lang::prelude::*;
2095    /// # use futures::StreamExt;
2096    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2097    /// let tick = process.tick();
2098    /// let numbers = process
2099    ///     .source_iter(q!(vec![(1, false), (2, true), (1, false), (2, false)]))
2100    ///     .into_keyed();
2101    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2102    /// batch
2103    ///     .reduce(q!(|acc, x| *acc |= x))
2104    ///     .entries()
2105    ///     .all_ticks()
2106    /// # }, |mut stream| async move {
2107    /// // (1, false), (2, true)
2108    /// # let mut results = Vec::new();
2109    /// # for _ in 0..2 {
2110    /// #     results.push(stream.next().await.unwrap());
2111    /// # }
2112    /// # results.sort();
2113    /// # assert_eq!(results, vec![(1, false), (2, true)]);
2114    /// # }));
2115    /// # }
2116    /// ```
2117    pub fn reduce<F: Fn(&mut V, V) + 'a, C, Idemp>(
2118        self,
2119        comb: impl IntoQuotedMut<'a, F, OperatorContext<L, B>, AggFuncAlgebra<V, B, C, Idemp>>,
2120    ) -> KeyedSingleton<K, V, L, B>
2121    where
2122        K: Eq + Hash,
2123        C: ValidCommutativityFor<O>,
2124        Idemp: ValidIdempotenceFor<R>,
2125    {
2126        let (f, proof) =
2127            comb.splice_fn2_borrow_mut_ctx_props(&OperatorContext::<L, B>::new(&self.location));
2128        proof.register_proof(&f);
2129
2130        let ordered = self
2131            .assume_retries::<ExactlyOnce>(nondet!(/** the combinator function is idempotent */))
2132            .assume_ordering::<TotalOrder>(nondet!(/** the combinator function is commutative */));
2133
2134        KeyedSingleton::new(
2135            ordered.location.clone(),
2136            HydroNode::ReduceKeyed {
2137                f: f.into(),
2138                input: Box::new(ordered.ir_node.replace(HydroNode::Placeholder)),
2139                metadata: ordered
2140                    .location
2141                    .new_node_metadata(KeyedSingleton::<K, V, L, B>::collection_kind()),
2142            },
2143        )
2144        .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
2145    }
2146
2147    /// A special case of [`KeyedStream::reduce`] where tuples with keys less than the watermark
2148    /// are automatically deleted.
2149    ///
2150    /// Depending on the input stream guarantees, the closure may need to be commutative
2151    /// (for unordered streams) or idempotent (for streams with non-deterministic duplicates).
2152    ///
2153    /// # Example
2154    /// ```rust
2155    /// # #[cfg(feature = "deploy")] {
2156    /// # use hydro_lang::prelude::*;
2157    /// # use futures::StreamExt;
2158    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2159    /// let tick = process.tick();
2160    /// let watermark = tick.singleton(q!(2));
2161    /// let numbers = process
2162    ///     .source_iter(q!([(0, false), (1, false), (2, false), (2, true)]))
2163    ///     .into_keyed();
2164    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2165    /// batch
2166    ///     .reduce_watermark(watermark, q!(|acc, x| *acc |= x))
2167    ///     .entries()
2168    ///     .all_ticks()
2169    /// # }, |mut stream| async move {
2170    /// // (2, true)
2171    /// # assert_eq!(stream.next().await.unwrap(), (2, true));
2172    /// # }));
2173    /// # }
2174    /// ```
2175    pub fn reduce_watermark<O2, F, C, Idemp>(
2176        self,
2177        other: impl Into<Optional<O2, Tick<L::Root>, Bounded>>,
2178        comb: impl IntoQuotedMut<'a, F, OperatorContext<L, B>, AggFuncAlgebra<V, B, C, Idemp>>,
2179    ) -> KeyedSingleton<K, V, L, B>
2180    where
2181        K: Eq + Hash,
2182        O2: Clone,
2183        F: Fn(&mut V, V) + 'a,
2184        C: ValidCommutativityFor<O>,
2185        Idemp: ValidIdempotenceFor<R>,
2186    {
2187        let other: Optional<O2, Tick<L::Root>, Bounded> = other.into();
2188        check_matching_location(&self.location.root(), other.location.parent_location());
2189        let (f, proof) =
2190            comb.splice_fn2_borrow_mut_ctx_props(&OperatorContext::<L, B>::new(&self.location));
2191        proof.register_proof(&f);
2192
2193        let ordered = self
2194            .assume_retries::<ExactlyOnce>(nondet!(/** the combinator function is idempotent */))
2195            .assume_ordering::<TotalOrder>(nondet!(/** the combinator function is commutative */));
2196
2197        KeyedSingleton::new(
2198            ordered.location.clone(),
2199            HydroNode::ReduceKeyedWatermark {
2200                f: f.into(),
2201                input: Box::new(ordered.ir_node.replace(HydroNode::Placeholder)),
2202                watermark: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2203                metadata: ordered
2204                    .location
2205                    .new_node_metadata(KeyedSingleton::<K, V, L, B>::collection_kind()),
2206            },
2207        )
2208        .assert_has_consistency_of(manual_proof!(/** algebraic properties */))
2209    }
2210
2211    /// Given a bounded stream of keys `K`, returns a new keyed stream containing only the groups
2212    /// whose keys are not in the bounded stream.
2213    ///
2214    /// # Example
2215    /// ```rust
2216    /// # #[cfg(feature = "deploy")] {
2217    /// # use hydro_lang::prelude::*;
2218    /// # use futures::StreamExt;
2219    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2220    /// let tick = process.tick();
2221    /// let keyed_stream = process
2222    ///     .source_iter(q!(vec![ (1, 'a'), (2, 'b'), (3, 'c'), (4, 'd') ]))
2223    ///     .batch(&tick, nondet!(/** test */))
2224    ///     .into_keyed();
2225    /// let keys_to_remove = process
2226    ///     .source_iter(q!(vec![1, 2]))
2227    ///     .batch(&tick, nondet!(/** test */));
2228    /// keyed_stream.filter_key_not_in(keys_to_remove).all_ticks()
2229    /// #   .entries()
2230    /// # }, |mut stream| async move {
2231    /// // { 3: ['c'], 4: ['d'] }
2232    /// # let mut results = Vec::new();
2233    /// # for _ in 0..2 {
2234    /// #     results.push(stream.next().await.unwrap());
2235    /// # }
2236    /// # results.sort();
2237    /// # assert_eq!(results, vec![(3, 'c'), (4, 'd')]);
2238    /// # }));
2239    /// # }
2240    /// ```
2241    pub fn filter_key_not_in<O2: Ordering, R2: Retries>(
2242        self,
2243        other: Stream<K, L, Bounded, O2, R2>,
2244    ) -> Self
2245    where
2246        K: Eq + Hash,
2247    {
2248        check_matching_location(&self.location, &other.location);
2249
2250        KeyedStream::new(
2251            self.location.clone(),
2252            HydroNode::AntiJoin {
2253                pos: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2254                neg: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2255                metadata: self.location.new_node_metadata(Self::collection_kind()),
2256            },
2257        )
2258    }
2259
2260    /// Emit a keyed stream containing keys shared between two keyed streams,
2261    /// where each value in the output keyed stream is a tuple of
2262    /// (self's value, other's value).
2263    /// If there are multiple values for the same key, this performs a cross product
2264    /// for each matching key.
2265    ///
2266    /// # Example
2267    /// ```rust
2268    /// # #[cfg(feature = "deploy")] {
2269    /// # use hydro_lang::prelude::*;
2270    /// # use futures::StreamExt;
2271    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2272    /// let tick = process.tick();
2273    /// let keyed_data = process
2274    ///     .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2275    ///     .into_keyed()
2276    ///     .batch(&tick, nondet!(/** test */));
2277    /// let other_data = process
2278    ///     .source_iter(q!(vec![(1, 100), (2, 200), (2, 201)]))
2279    ///     .into_keyed()
2280    ///     .batch(&tick, nondet!(/** test */));
2281    /// keyed_data.join_keyed_stream(other_data).entries().all_ticks()
2282    /// # }, |mut stream| async move {
2283    /// // { 1: [(10, 100), (11, 100)], 2: [(20, 200), (20, 201)] } in any order
2284    /// # let mut results = vec![];
2285    /// # for _ in 0..4 {
2286    /// #     results.push(stream.next().await.unwrap());
2287    /// # }
2288    /// # results.sort();
2289    /// # assert_eq!(results, vec![(1, (10, 100)), (1, (11, 100)), (2, (20, 200)), (2, (20, 201))]);
2290    /// # }));
2291    /// # }
2292    /// ```
2293    pub fn join_keyed_stream<V2, B2: Boundedness, O2: Ordering, R2: Retries>(
2294        self,
2295        other: KeyedStream<K, V2, L, B2, O2, R2>,
2296    ) -> KeyedStream<
2297        K,
2298        (V, V2),
2299        L,
2300        B,
2301        B2::PreserveOrderIfBounded<NoOrder>,
2302        <R as MinRetries<R2>>::Min,
2303    >
2304    where
2305        K: Eq + Hash + Clone,
2306        R: MinRetries<R2>,
2307        V: Clone,
2308        V2: Clone,
2309    {
2310        self.entries().join(other.entries()).into_keyed()
2311    }
2312
2313    /// Deduplicates values within each key group, emitting each unique value per key
2314    /// exactly once.
2315    ///
2316    /// # Example
2317    /// ```rust
2318    /// # #[cfg(feature = "deploy")] {
2319    /// # use hydro_lang::prelude::*;
2320    /// # use futures::StreamExt;
2321    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2322    /// process
2323    ///     .source_iter(q!(vec![(1, 10), (2, 20), (1, 10), (2, 30), (1, 20)]))
2324    ///     .into_keyed()
2325    ///     .unique()
2326    /// # .entries()
2327    /// # }, |mut stream| async move {
2328    /// // unique values per key: { 1: [10, 20], 2: [20, 30] }
2329    /// # let mut results = Vec::new();
2330    /// # for _ in 0..4 {
2331    /// #     results.push(stream.next().await.unwrap());
2332    /// # }
2333    /// # let mut key1: Vec<_> = results.iter().filter(|(k, _)| *k == 1).map(|(_, v)| *v).collect();
2334    /// # let mut key2: Vec<_> = results.iter().filter(|(k, _)| *k == 2).map(|(_, v)| *v).collect();
2335    /// # key1.sort();
2336    /// # key2.sort();
2337    /// # assert_eq!(key1, vec![10, 20]);
2338    /// # assert_eq!(key2, vec![20, 30]);
2339    /// # }));
2340    /// # }
2341    /// ```
2342    pub fn unique(self) -> KeyedStream<K, V, L, B, NoOrder, ExactlyOnce>
2343    where
2344        K: Eq + Hash + Clone,
2345        V: Eq + Hash + Clone,
2346    {
2347        self.entries().unique().into_keyed()
2348    }
2349
2350    /// Sorts the values within each key group in ascending order.
2351    ///
2352    /// The output keyed stream has a [`TotalOrder`] guarantee on the values within
2353    /// each group. This operator will block until all elements in the input stream
2354    /// are available, so it requires the input stream to be [`Bounded`].
2355    ///
2356    /// # Example
2357    /// ```rust
2358    /// # #[cfg(feature = "deploy")] {
2359    /// # use hydro_lang::prelude::*;
2360    /// # use futures::StreamExt;
2361    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2362    /// let tick = process.tick();
2363    /// let numbers = process
2364    ///     .source_iter(q!(vec![(1, 3), (2, 1), (1, 1), (2, 2)]))
2365    ///     .into_keyed();
2366    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2367    /// batch.sort().all_ticks()
2368    /// # .entries()
2369    /// # }, |mut stream| async move {
2370    /// // values sorted within each key: { 1: [1, 3], 2: [1, 2] }
2371    /// # let mut results = Vec::new();
2372    /// # for _ in 0..4 {
2373    /// #     results.push(stream.next().await.unwrap());
2374    /// # }
2375    /// # let key1_vals: Vec<_> = results.iter().filter(|(k, _)| *k == 1).map(|(_, v)| *v).collect();
2376    /// # let key2_vals: Vec<_> = results.iter().filter(|(k, _)| *k == 2).map(|(_, v)| *v).collect();
2377    /// # assert_eq!(key1_vals, vec![1, 3]);
2378    /// # assert_eq!(key2_vals, vec![1, 2]);
2379    /// # }));
2380    /// # }
2381    /// ```
2382    pub fn sort(self) -> KeyedStream<K, V, L, Bounded, TotalOrder, R>
2383    where
2384        B: IsBounded,
2385        K: Ord,
2386        V: Ord,
2387    {
2388        self.entries().sort().into_keyed()
2389    }
2390
2391    /// Produces a new keyed stream that combines the groups of the inputs by first emitting the
2392    /// elements of the `self` stream, and then emits the elements of the `other` stream (if a key
2393    /// is only present in one of the inputs, its values are passed through as-is). The output has
2394    /// a [`TotalOrder`] guarantee if and only if both inputs have a [`TotalOrder`] guarantee.
2395    ///
2396    /// Currently, both input streams must be [`Bounded`]. This operator will block
2397    /// on the first stream until all its elements are available. In a future version,
2398    /// we will relax the requirement on the `other` stream.
2399    ///
2400    /// # Example
2401    /// ```rust
2402    /// # #[cfg(feature = "deploy")] {
2403    /// # use hydro_lang::prelude::*;
2404    /// # use futures::StreamExt;
2405    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2406    /// let tick = process.tick();
2407    /// let numbers = process.source_iter(q!(vec![(0, 1), (1, 3)])).into_keyed();
2408    /// let batch = numbers.batch(&tick, nondet!(/** test */));
2409    /// batch.clone().map(q!(|x| x + 1)).chain(batch).all_ticks()
2410    /// # .entries()
2411    /// # }, |mut stream| async move {
2412    /// // { 0: [2, 1], 1: [4, 3] }
2413    /// # let mut results = Vec::new();
2414    /// # for _ in 0..4 {
2415    /// #     results.push(stream.next().await.unwrap());
2416    /// # }
2417    /// # results.sort();
2418    /// # assert_eq!(results, vec![(0, 1), (0, 2), (1, 3), (1, 4)]);
2419    /// # }));
2420    /// # }
2421    /// ```
2422    pub fn chain<O2: Ordering, R2: Retries>(
2423        self,
2424        other: KeyedStream<K, V, L, Bounded, O2, R2>,
2425    ) -> KeyedStream<K, V, L, Bounded, <O as MinOrder<O2>>::Min, <R as MinRetries<R2>>::Min>
2426    where
2427        B: IsBounded,
2428        O: MinOrder<O2>,
2429        R: MinRetries<R2>,
2430    {
2431        let this = self.make_bounded();
2432        check_matching_location(&this.location, &other.location);
2433
2434        KeyedStream::new(
2435            this.location.clone(),
2436            HydroNode::Chain {
2437                first: Box::new(this.ir_node.replace(HydroNode::Placeholder)),
2438                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2439                metadata: this.location.new_node_metadata(KeyedStream::<
2440                    K,
2441                    V,
2442                    L,
2443                    Bounded,
2444                    <O as MinOrder<O2>>::Min,
2445                    <R as MinRetries<R2>>::Min,
2446                >::collection_kind()),
2447            },
2448        )
2449    }
2450
2451    /// Emit a keyed stream containing keys shared between the keyed stream and the
2452    /// keyed singleton, where each value in the output keyed stream is a tuple of
2453    /// (the keyed stream's value, the keyed singleton's value).
2454    ///
2455    /// # Example
2456    /// ```rust
2457    /// # #[cfg(feature = "deploy")] {
2458    /// # use hydro_lang::prelude::*;
2459    /// # use futures::StreamExt;
2460    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2461    /// let tick = process.tick();
2462    /// let keyed_data = process
2463    ///     .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2464    ///     .into_keyed()
2465    ///     .batch(&tick, nondet!(/** test */));
2466    /// let singleton_data = process
2467    ///     .source_iter(q!(vec![(1, 100), (2, 200)]))
2468    ///     .into_keyed()
2469    ///     .batch(&tick, nondet!(/** test */))
2470    ///     .first();
2471    /// keyed_data.join_keyed_singleton(singleton_data).entries().all_ticks()
2472    /// # }, |mut stream| async move {
2473    /// // { 1: [(10, 100), (11, 100)], 2: [(20, 200)] } in any order
2474    /// # let mut results = vec![];
2475    /// # for _ in 0..3 {
2476    /// #     results.push(stream.next().await.unwrap());
2477    /// # }
2478    /// # results.sort();
2479    /// # assert_eq!(results, vec![(1, (10, 100)), (1, (11, 100)), (2, (20, 200))]);
2480    /// # }));
2481    /// # }
2482    /// ```
2483    pub fn join_keyed_singleton<V2: Clone, B2: IsBounded>(
2484        self,
2485        other: KeyedSingleton<K, V2, L, B2>,
2486    ) -> KeyedStream<K, (V, V2), L, B, O, R>
2487    where
2488        K: Eq + Hash + Clone,
2489        V: Clone,
2490    {
2491        let ir_node = if B2::BOUNDED {
2492            HydroNode::JoinHalf {
2493                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2494                right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2495                metadata: self
2496                    .location
2497                    .new_node_metadata(KeyedStream::<K, (V, V2), L, B, O, R>::collection_kind()),
2498            }
2499        } else {
2500            HydroNode::Join {
2501                left: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2502                right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2503                metadata: self
2504                    .location
2505                    .new_node_metadata(KeyedStream::<K, (V, V2), L, B, O, R>::collection_kind()),
2506            }
2507        };
2508
2509        KeyedStream::new(self.location.clone(), ir_node)
2510    }
2511
2512    /// Gets the values associated with a specific key from the keyed stream.
2513    /// Returns an empty stream if the key is `None` or there are no associated values.
2514    ///
2515    /// # Example
2516    /// ```rust
2517    /// # #[cfg(feature = "deploy")] {
2518    /// # use hydro_lang::prelude::*;
2519    /// # use futures::StreamExt;
2520    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2521    /// let tick = process.tick();
2522    /// let keyed_data = process
2523    ///     .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2524    ///     .into_keyed()
2525    ///     .batch(&tick, nondet!(/** test */));
2526    /// let key = tick.singleton(q!(1));
2527    /// keyed_data.get(key).all_ticks()
2528    /// # }, |mut stream| async move {
2529    /// // 10, 11
2530    /// # let mut results = vec![];
2531    /// # for _ in 0..2 {
2532    /// #     results.push(stream.next().await.unwrap());
2533    /// # }
2534    /// # results.sort();
2535    /// # assert_eq!(results, vec![10, 11]);
2536    /// # }));
2537    /// # }
2538    /// ```
2539    pub fn get(self, key: impl Into<Optional<K, L, Bounded>>) -> Stream<V, L, B, O, R>
2540    where
2541        K: Eq + Hash + Clone,
2542        V: Clone,
2543    {
2544        let joined =
2545            self.join_keyed_singleton(key.into().map(q!(|k| (k, ()))).into_keyed_singleton());
2546
2547        if O::ORDERING_KIND == StreamOrder::TotalOrder {
2548            joined
2549                .use_ordering_type::<TotalOrder>()
2550                .cast_at_most_one_key()
2551                .map(q!(|(_, (v, _))| v))
2552                .weaken_ordering()
2553        } else {
2554            joined.values().map(q!(|(v, _)| v)).use_ordering_type()
2555        }
2556    }
2557
2558    /// For each value in `self`, find the matching key in `lookup`.
2559    /// The output is a keyed stream with the key from `self`, and a value
2560    /// that is a tuple of (`self`'s value, Option<`lookup`'s value>).
2561    /// If the key is not present in `lookup`, the option will be [`None`].
2562    ///
2563    /// # Example
2564    /// ```rust
2565    /// # #[cfg(feature = "deploy")] {
2566    /// # use hydro_lang::prelude::*;
2567    /// # use futures::StreamExt;
2568    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2569    /// # let tick = process.tick();
2570    /// let requests = // { 1: [10, 11], 2: 20 }
2571    /// # process
2572    /// #     .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2573    /// #     .into_keyed()
2574    /// #     .batch(&tick, nondet!(/** test */));
2575    /// let other_data = // { 10: 100, 11: 110 }
2576    /// # process
2577    /// #     .source_iter(q!(vec![(10, 100), (11, 110)]))
2578    /// #     .into_keyed()
2579    /// #     .batch(&tick, nondet!(/** test */))
2580    /// #     .first();
2581    /// requests.lookup_keyed_singleton(other_data)
2582    /// # .entries().all_ticks()
2583    /// # }, |mut stream| async move {
2584    /// // { 1: [(10, Some(100)), (11, Some(110))], 2: (20, None) }
2585    /// # let mut results = vec![];
2586    /// # for _ in 0..3 {
2587    /// #     results.push(stream.next().await.unwrap());
2588    /// # }
2589    /// # results.sort();
2590    /// # assert_eq!(results, vec![(1, (10, Some(100))), (1, (11, Some(110))), (2, (20, None))]);
2591    /// # }));
2592    /// # }
2593    /// ```
2594    pub fn lookup_keyed_singleton<V2>(
2595        self,
2596        lookup: KeyedSingleton<V, V2, L, Bounded>,
2597    ) -> KeyedStream<K, (V, Option<V2>), L, Bounded, NoOrder, R>
2598    where
2599        B: IsBounded,
2600        K: Eq + Hash + Clone,
2601        V: Eq + Hash + Clone,
2602        V2: Clone,
2603    {
2604        self.lookup_keyed_stream(lookup.into_keyed_stream().weaken_retries::<R>())
2605    }
2606
2607    /// For each value in `self`, find the matching key in `lookup`.
2608    /// The output is a keyed stream with the key from `self`, and a value
2609    /// that is a tuple of (`self`'s value, Option<`lookup`'s value>).
2610    /// If the key is not present in `lookup`, the option will be [`None`].
2611    ///
2612    /// # Example
2613    /// ```rust
2614    /// # #[cfg(feature = "deploy")] {
2615    /// # use hydro_lang::prelude::*;
2616    /// # use futures::StreamExt;
2617    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2618    /// # let tick = process.tick();
2619    /// let requests = // { 1: [10, 11], 2: 20 }
2620    /// # process
2621    /// #     .source_iter(q!(vec![(1, 10), (1, 11), (2, 20)]))
2622    /// #     .into_keyed()
2623    /// #     .batch(&tick, nondet!(/** test */));
2624    /// let other_data = // { 10: [100, 101], 11: 110 }
2625    /// # process
2626    /// #     .source_iter(q!(vec![(10, 100), (10, 101), (11, 110)]))
2627    /// #     .into_keyed()
2628    /// #     .batch(&tick, nondet!(/** test */));
2629    /// requests.lookup_keyed_stream(other_data)
2630    /// # .entries().all_ticks()
2631    /// # }, |mut stream| async move {
2632    /// // { 1: [(10, Some(100)), (10, Some(101)), (11, Some(110))], 2: (20, None) }
2633    /// # let mut results = vec![];
2634    /// # for _ in 0..4 {
2635    /// #     results.push(stream.next().await.unwrap());
2636    /// # }
2637    /// # results.sort();
2638    /// # assert_eq!(results, vec![(1, (10, Some(100))), (1, (10, Some(101))), (1, (11, Some(110))), (2, (20, None))]);
2639    /// # }));
2640    /// # }
2641    /// ```
2642    pub fn lookup_keyed_stream<V2, O2: Ordering, R2: Retries>(
2643        self,
2644        lookup: KeyedStream<V, V2, L, Bounded, O2, R2>,
2645    ) -> KeyedStream<K, (V, Option<V2>), L, Bounded, NoOrder, <R as MinRetries<R2>>::Min>
2646    where
2647        B: IsBounded,
2648        K: Eq + Hash + Clone,
2649        V: Eq + Hash + Clone,
2650        V2: Clone,
2651        R: MinRetries<R2>,
2652    {
2653        let inverted = self
2654            .make_bounded()
2655            .entries()
2656            .map(q!(|(key, lookup_value)| (lookup_value, key)))
2657            .into_keyed();
2658        let found = inverted
2659            .clone()
2660            .join_keyed_stream(lookup.clone())
2661            .entries()
2662            .map(q!(|(lookup_value, (key, value))| (
2663                key,
2664                (lookup_value, Some(value))
2665            )))
2666            .into_keyed();
2667        let not_found = inverted
2668            .filter_key_not_in(lookup.keys())
2669            .entries()
2670            .map(q!(|(lookup_value, key)| (key, (lookup_value, None))))
2671            .into_keyed();
2672
2673        found.chain(not_found.weaken_retries::<<R as MinRetries<R2>>::Min>())
2674    }
2675
2676    /// Shifts this keyed stream into an atomic context, which guarantees that any downstream logic
2677    /// will all be executed synchronously before any outputs are yielded (in [`KeyedStream::end_atomic`]).
2678    ///
2679    /// This is useful to enforce local consistency constraints, such as ensuring that a write is
2680    /// processed before an acknowledgement is emitted.
2681    pub fn atomic(self) -> KeyedStream<K, V, Atomic<L>, B, O, R>
2682    where
2683        L: TopLevel<'a>,
2684    {
2685        let out_location = Atomic {
2686            tick: self.location.tick(),
2687        };
2688        KeyedStream::new(
2689            out_location.clone(),
2690            HydroNode::BeginAtomic {
2691                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2692                metadata: out_location
2693                    .new_node_metadata(KeyedStream::<K, V, Atomic<L>, B, O, R>::collection_kind()),
2694            },
2695        )
2696    }
2697
2698    /// Given a tick, returns a keyed stream corresponding to a batch of elements segmented by
2699    /// that tick. These batches are guaranteed to be contiguous across ticks and preserve
2700    /// the order of the input.
2701    ///
2702    /// # Non-Determinism
2703    /// The batch boundaries are non-deterministic and may change across executions.
2704    ///
2705    /// In simulation tests, the batching decisions can be scripted by attaching a
2706    /// [`KeyedBatchHook`](crate::sim_hooks::KeyedBatchHook) to the guard via
2707    /// `nondet!(/** reason */ hook = my_hook)`.
2708    pub fn batch<L2: Location<'a, DropConsistency = L::DropConsistency>>(
2709        self,
2710        tick: &Tick<L2>,
2711        mut nondet: NonDet<Option<crate::sim_hooks::KeyedBatchHook<K, V, O, R>>>,
2712    ) -> KeyedStream<K, V, Tick<L::DropConsistency>, Bounded, O, R> {
2713        assert_eq!(
2714            Location::id(tick.parent_location()),
2715            Location::id(&self.location)
2716        );
2717        let mut metadata =
2718            tick.new_node_metadata(KeyedStream::<K, V, Tick<L>, Bounded, O, R>::collection_kind());
2719        metadata.op.sim_hook_id = nondet.take_hook().map(|h| h.id);
2720        KeyedStream::new(
2721            tick.drop_consistency(),
2722            HydroNode::Batch {
2723                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2724                metadata,
2725            },
2726        )
2727    }
2728}
2729
2730impl<'a, K1, K2, V, L: Location<'a>, B: Boundedness, O: Ordering, R: Retries>
2731    KeyedStream<(K1, K2), V, L, B, O, R>
2732{
2733    /// Produces a new keyed stream by dropping the first element of the compound key.
2734    ///
2735    /// Because multiple keys may share the same suffix, this operation results in re-grouping
2736    /// of the values under the new keys. The values across groups with the same new key
2737    /// will be interleaved, so the resulting stream has [`NoOrder`] within each group.
2738    ///
2739    /// # Example
2740    /// ```rust
2741    /// # #[cfg(feature = "deploy")] {
2742    /// # use hydro_lang::prelude::*;
2743    /// # use futures::StreamExt;
2744    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2745    /// process
2746    ///     .source_iter(q!(vec![((1, 10), 2), ((1, 10), 3), ((2, 20), 4)]))
2747    ///     .into_keyed()
2748    ///     .drop_key_prefix()
2749    /// #   .entries()
2750    /// # }, |mut stream| async move {
2751    /// // { 10: [2, 3], 20: [4] }
2752    /// # let mut results = Vec::new();
2753    /// # for _ in 0..3 {
2754    /// #     results.push(stream.next().await.unwrap());
2755    /// # }
2756    /// # results.sort();
2757    /// # assert_eq!(results, vec![(10, 2), (10, 3), (20, 4)]);
2758    /// # }));
2759    /// # }
2760    /// ```
2761    pub fn drop_key_prefix(self) -> KeyedStream<K2, V, L, B, NoOrder, R> {
2762        self.entries()
2763            .map(q!(|((_k1, k2), v)| (k2, v)))
2764            .into_keyed()
2765    }
2766}
2767
2768impl<'a, K, V, L: Location<'a>, O: Ordering, R: Retries> KeyedStream<K, V, L, Unbounded, O, R> {
2769    /// Produces a new keyed stream that "merges" the inputs by interleaving the elements
2770    /// of any overlapping groups. The result has [`NoOrder`] on each group because the
2771    /// order of interleaving is not guaranteed. If the keys across both inputs do not overlap,
2772    /// the ordering will be deterministic and you can safely use [`Self::assume_ordering`].
2773    ///
2774    /// Currently, both input streams must be [`Unbounded`].
2775    ///
2776    /// # Example
2777    /// ```rust
2778    /// # #[cfg(feature = "deploy")] {
2779    /// # use hydro_lang::prelude::*;
2780    /// # use futures::StreamExt;
2781    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2782    /// let numbers1: KeyedStream<i32, i32, _> = // { 1: [2], 3: [4] }
2783    /// # process.source_iter(q!(vec![(1, 2), (3, 4)])).into_keyed().into();
2784    /// let numbers2: KeyedStream<i32, i32, _> = // { 1: [3], 3: [5] }
2785    /// # process.source_iter(q!(vec![(1, 3), (3, 5)])).into_keyed().into();
2786    /// numbers1.merge_unordered(numbers2)
2787    /// #   .entries()
2788    /// # }, |mut stream| async move {
2789    /// // { 1: [2, 3], 3: [4, 5] } with each group in unknown order
2790    /// # let mut results = Vec::new();
2791    /// # for _ in 0..4 {
2792    /// #     results.push(stream.next().await.unwrap());
2793    /// # }
2794    /// # results.sort();
2795    /// # assert_eq!(results, vec![(1, 2), (1, 3), (3, 4), (3, 5)]);
2796    /// # }));
2797    /// # }
2798    /// ```
2799    pub fn merge_unordered<O2: Ordering, R2: Retries>(
2800        self,
2801        other: KeyedStream<K, V, L, Unbounded, O2, R2>,
2802    ) -> KeyedStream<K, V, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2803    where
2804        R: MinRetries<R2>,
2805    {
2806        KeyedStream::new(
2807            self.location.clone(),
2808            HydroNode::Chain {
2809                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2810                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2811                metadata: self.location.new_node_metadata(KeyedStream::<
2812                    K,
2813                    V,
2814                    L,
2815                    Unbounded,
2816                    NoOrder,
2817                    <R as MinRetries<R2>>::Min,
2818                >::collection_kind()),
2819            },
2820        )
2821    }
2822
2823    /// Deprecated: use [`KeyedStream::merge_unordered`] instead.
2824    #[deprecated(note = "use `merge_unordered` instead")]
2825    pub fn interleave<O2: Ordering, R2: Retries>(
2826        self,
2827        other: KeyedStream<K, V, L, Unbounded, O2, R2>,
2828    ) -> KeyedStream<K, V, L, Unbounded, NoOrder, <R as MinRetries<R2>>::Min>
2829    where
2830        R: MinRetries<R2>,
2831    {
2832        self.merge_unordered(other)
2833    }
2834}
2835
2836impl<'a, K, V, L: Location<'a>, B: Boundedness, R: Retries> KeyedStream<K, V, L, B, TotalOrder, R> {
2837    /// Produces a new keyed stream that combines the elements of the two input keyed streams,
2838    /// preserving the relative order of elements within each group of each input.
2839    ///
2840    /// Because each group in both inputs is [`TotalOrder`], the output preserves the relative
2841    /// order of elements within each group of each input, and the result is [`TotalOrder`].
2842    ///
2843    /// # Non-Determinism
2844    /// For groups whose key appears in both inputs, the order in which the elements of the two
2845    /// inputs are interleaved *within that group* is non-deterministic, so the order of elements
2846    /// will vary across runs. If the keys across both inputs do not overlap, the ordering is
2847    /// deterministic. If the output order within each group is irrelevant, use
2848    /// [`KeyedStream::merge_unordered`] instead, which is deterministic but emits an unordered
2849    /// keyed stream.
2850    ///
2851    /// In simulation tests, the interleaving decisions can be scripted by attaching a
2852    /// [`KeyedMergeOrderedHook`](crate::sim_hooks::KeyedMergeOrderedHook) to the guard via
2853    /// `nondet!(/** reason */ hook = my_hook)`.
2854    ///
2855    /// # Example
2856    /// ```rust
2857    /// # #[cfg(feature = "deploy")] {
2858    /// # use hydro_lang::prelude::*;
2859    /// # use futures::StreamExt;
2860    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
2861    /// let numbers1: KeyedStream<i32, i32, _> = // { 1: [2], 3: [4] }
2862    /// # process.source_iter(q!(vec![(1, 2), (3, 4)])).into_keyed().into();
2863    /// let numbers2: KeyedStream<i32, i32, _> = // { 1: [3], 3: [5] }
2864    /// # process.source_iter(q!(vec![(1, 3), (3, 5)])).into_keyed().into();
2865    /// numbers1.merge_ordered(numbers2, nondet!(/** example */))
2866    /// #   .entries()
2867    /// # }, |mut stream| async move {
2868    /// // { 1: [2, 3], 3: [4, 5] } with each group interleaved in some order
2869    /// # let mut results = Vec::new();
2870    /// # for _ in 0..4 {
2871    /// #     results.push(stream.next().await.unwrap());
2872    /// # }
2873    /// # results.sort();
2874    /// # assert_eq!(results, vec![(1, 2), (1, 3), (3, 4), (3, 5)]);
2875    /// # }));
2876    /// # }
2877    /// ```
2878    pub fn merge_ordered<R2: Retries>(
2879        self,
2880        other: KeyedStream<K, V, L, B, TotalOrder, R2>,
2881        mut nondet: NonDet<Option<crate::sim_hooks::KeyedMergeOrderedHook<K, V, B>>>,
2882    ) -> KeyedStream<K, V, L::DropConsistency, B, TotalOrder, <R as MinRetries<R2>>::Min>
2883    where
2884        R: MinRetries<R2>,
2885    {
2886        let target_location = self.location.drop_consistency();
2887        let mut metadata = target_location.new_node_metadata(KeyedStream::<
2888            K,
2889            V,
2890            L::DropConsistency,
2891            B,
2892            TotalOrder,
2893            <R as MinRetries<R2>>::Min,
2894        >::collection_kind());
2895        metadata.op.sim_hook_id = nondet.take_hook().map(|hook| hook.id);
2896        KeyedStream::new(
2897            target_location,
2898            HydroNode::MergeOrdered {
2899                first: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2900                second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
2901                metadata,
2902            },
2903        )
2904    }
2905}
2906
2907impl<'a, K, V, L, B: Boundedness, O: Ordering, R: Retries> KeyedStream<K, V, Atomic<L>, B, O, R>
2908where
2909    L: Location<'a>,
2910{
2911    /// Returns a keyed stream corresponding to the latest batch of elements being atomically
2912    /// processed. These batches are guaranteed to be contiguous across ticks and preserve
2913    /// the order of the input. The output keyed stream will execute in the [`Tick`] that was
2914    /// used to create the atomic section.
2915    ///
2916    /// # Non-Determinism
2917    /// The batch boundaries are non-deterministic and may change across executions.
2918    pub fn batch_atomic<L2: Location<'a, DropConsistency = L::DropConsistency>>(
2919        self,
2920        tick: &Tick<L2>,
2921        nondet: NonDet,
2922    ) -> KeyedStream<K, V, Tick<L::DropConsistency>, Bounded, O, R> {
2923        let _ = nondet;
2924        assert_eq!(
2925            Location::id(tick.parent_location()),
2926            Location::id(self.location.tick.parent_location())
2927        );
2928        KeyedStream::new(
2929            tick.drop_consistency(),
2930            HydroNode::Batch {
2931                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2932                metadata: tick.new_node_metadata(
2933                    KeyedStream::<K, V, Tick<L>, Bounded, O, R>::collection_kind(),
2934                ),
2935            },
2936        )
2937    }
2938
2939    /// Yields the elements of this keyed stream back into a top-level, asynchronous execution context.
2940    /// See [`KeyedStream::atomic`] for more details.
2941    pub fn end_atomic(self) -> KeyedStream<K, V, L, B, O, R> {
2942        KeyedStream::new(
2943            self.location.tick.l.clone(),
2944            HydroNode::EndAtomic {
2945                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2946                metadata: self
2947                    .location
2948                    .tick
2949                    .l
2950                    .new_node_metadata(KeyedStream::<K, V, L, B, O, R>::collection_kind()),
2951            },
2952        )
2953    }
2954}
2955
2956impl<'a, K, V, L, O: Ordering, R: Retries> KeyedStream<K, V, Tick<L>, Bounded, O, R>
2957where
2958    L: Location<'a>,
2959{
2960    /// Asynchronously yields this batch of keyed elements outside the tick as an unbounded keyed stream,
2961    /// which will stream all the elements across _all_ tick iterations by concatenating the batches for
2962    /// each key.
2963    pub fn all_ticks(self) -> KeyedStream<K, V, L, Unbounded, O, R> {
2964        KeyedStream::new(
2965            self.location.parent_location().clone(),
2966            HydroNode::YieldConcat {
2967                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2968                metadata: self
2969                    .location
2970                    .parent_location()
2971                    .new_node_metadata(KeyedStream::<K, V, L, Unbounded, O, R>::collection_kind()),
2972            },
2973        )
2974    }
2975
2976    /// Synchronously yields this batch of keyed elements outside the tick as an unbounded keyed stream,
2977    /// which will stream all the elements across _all_ tick iterations by concatenating the batches for
2978    /// each key.
2979    ///
2980    /// Unlike [`KeyedStream::all_ticks`], this preserves synchronous execution, as the output stream
2981    /// is emitted in an [`Atomic`] context that will process elements synchronously with the input
2982    /// stream's [`Tick`] context.
2983    pub fn all_ticks_atomic(self) -> KeyedStream<K, V, Atomic<L>, Unbounded, O, R> {
2984        let out_location = Atomic {
2985            tick: self.location.clone(),
2986        };
2987
2988        KeyedStream::new(
2989            out_location.clone(),
2990            HydroNode::YieldConcat {
2991                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
2992                metadata: out_location.new_node_metadata(KeyedStream::<
2993                    K,
2994                    V,
2995                    Atomic<L>,
2996                    Unbounded,
2997                    O,
2998                    R,
2999                >::collection_kind()),
3000            },
3001        )
3002    }
3003
3004    /// Transforms the keyed stream using the given closure in "stateful" mode, where stateful operators
3005    /// such as `fold` retrain their memory for each key across ticks rather than resetting across batches of each key.
3006    ///
3007    /// This API is particularly useful for stateful computation on batches of data, such as
3008    /// maintaining an accumulated state that is up to date with the current batch.
3009    ///
3010    /// # Example
3011    /// ```rust
3012    /// # #[cfg(feature = "deploy")] {
3013    /// # use hydro_lang::prelude::*;
3014    /// # use futures::StreamExt;
3015    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3016    /// let tick = process.tick();
3017    /// # // ticks are lazy by default, forces the second tick to run
3018    /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
3019    /// # let batch_first_tick = process
3020    /// #   .source_iter(q!(vec![(0, 1), (1, 2), (2, 3), (3, 4)]))
3021    /// #   .into_keyed()
3022    /// #   .batch(&tick, nondet!(/** test */));
3023    /// # let batch_second_tick = process
3024    /// #   .source_iter(q!(vec![(0, 5), (1, 6), (2, 7)]))
3025    /// #   .into_keyed()
3026    /// #   .batch(&tick, nondet!(/** test */))
3027    /// #   .defer_tick(); // appears on the second tick
3028    /// let input = batch_first_tick.chain(batch_second_tick);
3029    ///
3030    /// input
3031    ///     .across_ticks(|s| {
3032    ///         s.reduce(q!(|sum, new| {
3033    ///             *sum += new;
3034    ///         }))
3035    ///     })
3036    ///     .entries()
3037    ///     .all_ticks()
3038    /// # }, |mut stream| async move {
3039    /// // First tick: [(0, 1), (1, 2), (2, 3), (3, 4)]
3040    /// # let mut results = Vec::new();
3041    /// # for _ in 0..4 {
3042    /// #     results.push(stream.next().await.unwrap());
3043    /// # }
3044    /// # results.sort();
3045    /// # assert_eq!(results, vec![(0, 1), (1, 2), (2, 3), (3, 4)]);
3046    /// // Second tick: [(0, 6), (1, 8), (2, 10), (3, 4)]
3047    /// # results.clear();
3048    /// # for _ in 0..4 {
3049    /// #     results.push(stream.next().await.unwrap());
3050    /// # }
3051    /// # results.sort();
3052    /// # assert_eq!(results, vec![(0, 6), (1, 8), (2, 10), (3, 4)]);
3053    /// # }));
3054    /// # }
3055    /// ```
3056    pub fn across_ticks<Out: BatchAtomic<'a>>(
3057        self,
3058        thunk: impl FnOnce(KeyedStream<K, V, Atomic<L>, Unbounded, O, R>) -> Out,
3059    ) -> Out::Batched {
3060        thunk(self.all_ticks_atomic()).batched_atomic()
3061    }
3062
3063    /// Shifts the entries in `self` to the **next tick**, so that the returned keyed stream at
3064    /// tick `T` always has the entries of `self` at tick `T - 1`.
3065    ///
3066    /// At tick `0`, the output keyed stream is empty, since there is no previous tick.
3067    ///
3068    /// This operator enables stateful iterative processing with ticks, by sending data from one
3069    /// tick to the next. For example, you can use it to combine inputs across consecutive batches.
3070    ///
3071    /// # Example
3072    /// ```rust
3073    /// # #[cfg(feature = "deploy")] {
3074    /// # use hydro_lang::prelude::*;
3075    /// # use futures::StreamExt;
3076    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
3077    /// let tick = process.tick();
3078    /// # // ticks are lazy by default, forces the second tick to run
3079    /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
3080    /// # let batch_first_tick = process
3081    /// #   .source_iter(q!(vec![(1, 2), (1, 3)]))
3082    /// #   .batch(&tick, nondet!(/** test */))
3083    /// #   .into_keyed();
3084    /// # let batch_second_tick = process
3085    /// #   .source_iter(q!(vec![(1, 4), (2, 5)]))
3086    /// #   .batch(&tick, nondet!(/** test */))
3087    /// #   .defer_tick()
3088    /// #   .into_keyed(); // appears on the second tick
3089    /// let changes_across_ticks = // { 1: [2, 3] } (first tick), { 1: [4], 2: [5] } (second tick)
3090    /// # batch_first_tick.chain(batch_second_tick);
3091    /// changes_across_ticks.clone().defer_tick().chain( // from the previous tick
3092    ///     changes_across_ticks // from the current tick
3093    /// )
3094    /// # .entries().all_ticks()
3095    /// # }, |mut stream| async move {
3096    /// // First tick: { 1: [2, 3] }
3097    /// # let mut results = Vec::new();
3098    /// # for _ in 0..2 {
3099    /// #     results.push(stream.next().await.unwrap());
3100    /// # }
3101    /// # results.sort();
3102    /// # assert_eq!(results, vec![(1, 2), (1, 3)]);
3103    /// // Second tick: { 1: [2, 3, 4], 2: [5] }
3104    /// # results.clear();
3105    /// # for _ in 0..4 {
3106    /// #     results.push(stream.next().await.unwrap());
3107    /// # }
3108    /// # results.sort();
3109    /// # assert_eq!(results, vec![(1, 2), (1, 3), (1, 4), (2, 5)]);
3110    /// // Third tick: { 1: [4], 2: [5] }
3111    /// # results.clear();
3112    /// # for _ in 0..2 {
3113    /// #     results.push(stream.next().await.unwrap());
3114    /// # }
3115    /// # results.sort();
3116    /// # assert_eq!(results, vec![(1, 4), (2, 5)]);
3117    /// # }));
3118    /// # }
3119    /// ```
3120    pub fn defer_tick(self) -> KeyedStream<K, V, Tick<L>, Bounded, O, R> {
3121        KeyedStream::new(
3122            self.location.clone(),
3123            HydroNode::DeferTick {
3124                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
3125                metadata: self.location.new_node_metadata(KeyedStream::<
3126                    K,
3127                    V,
3128                    Tick<L>,
3129                    Bounded,
3130                    O,
3131                    R,
3132                >::collection_kind()),
3133            },
3134        )
3135    }
3136}
3137
3138#[cfg(test)]
3139mod tests {
3140    #[cfg(feature = "deploy")]
3141    use futures::{SinkExt, StreamExt};
3142    #[cfg(feature = "deploy")]
3143    use hydro_deploy::Deployment;
3144    #[cfg(any(feature = "deploy", feature = "sim"))]
3145    use stageleft::q;
3146
3147    #[cfg(any(feature = "deploy", feature = "sim"))]
3148    use crate::compile::builder::FlowBuilder;
3149    #[cfg(feature = "deploy")]
3150    use crate::live_collections::stream::ExactlyOnce;
3151    #[cfg(feature = "sim")]
3152    use crate::live_collections::stream::{NoOrder, TotalOrder};
3153    #[cfg(any(feature = "deploy", feature = "sim"))]
3154    use crate::location::Location;
3155    #[cfg(feature = "sim")]
3156    use crate::networking::TCP;
3157    #[cfg(any(feature = "deploy", feature = "sim"))]
3158    use crate::nondet::nondet;
3159    #[cfg(feature = "deploy")]
3160    use crate::properties::manual_proof;
3161
3162    #[cfg(feature = "deploy")]
3163    #[tokio::test]
3164    async fn get_unbounded_keyed_stream_bounded_singleton() {
3165        let mut deployment = Deployment::new();
3166
3167        let mut flow = FlowBuilder::new();
3168        let node = flow.process::<()>();
3169        let external = flow.external::<()>();
3170
3171        let (input_send, input_stream) =
3172            node.source_external_bincode::<_, (i32, i32), _, ExactlyOnce>(&external);
3173
3174        let key = node.singleton(q!(1));
3175
3176        let out = input_stream
3177            .into_keyed()
3178            .get(key)
3179            .send_bincode_external(&external);
3180
3181        let nodes = flow
3182            .with_process(&node, deployment.Localhost())
3183            .with_external(&external, deployment.Localhost())
3184            .deploy(&mut deployment);
3185
3186        deployment.deploy().await.unwrap();
3187
3188        let mut input_send = nodes.connect(input_send).await;
3189        let mut out = nodes.connect(out).await;
3190
3191        deployment.start().await.unwrap();
3192
3193        // First batch
3194        input_send.send((1, 10)).await.unwrap();
3195        input_send.send((2, 20)).await.unwrap();
3196        assert_eq!(out.next().await.unwrap(), 10);
3197
3198        // Second batch
3199        input_send.send((1, 11)).await.unwrap();
3200        input_send.send((2, 21)).await.unwrap();
3201        assert_eq!(out.next().await.unwrap(), 11);
3202    }
3203
3204    #[cfg(feature = "deploy")]
3205    #[tokio::test]
3206    async fn reduce_watermark_filter() {
3207        let mut deployment = Deployment::new();
3208
3209        let mut flow = FlowBuilder::new();
3210        let node = flow.process::<()>();
3211        let external = flow.external::<()>();
3212
3213        let node_tick = node.tick();
3214        let watermark = node_tick.singleton(q!(2));
3215
3216        // The reduce produces a top-level value driven by `node_tick`'s watermark. Snapshot it
3217        // in a *distinct* tick so we don't re-enter `node_tick` (which supplied an input to the
3218        // reduce), which is not representable as a DFIR loop.
3219        let snapshot_tick = node.tick();
3220        let sum = node
3221            .source_stream(q!(tokio_stream::iter([
3222                (0, 100),
3223                (1, 101),
3224                (2, 102),
3225                (2, 102)
3226            ])))
3227            .into_keyed()
3228            .reduce_watermark(
3229                watermark,
3230                q!(|acc, v| {
3231                    *acc += v;
3232                }),
3233            )
3234            .snapshot(&snapshot_tick, nondet!(/** test */))
3235            .entries()
3236            .all_ticks()
3237            .send_bincode_external(&external);
3238
3239        let nodes = flow
3240            .with_process(&node, deployment.Localhost())
3241            .with_external(&external, deployment.Localhost())
3242            .deploy(&mut deployment);
3243
3244        deployment.deploy().await.unwrap();
3245
3246        let mut out = nodes.connect(sum).await;
3247
3248        deployment.start().await.unwrap();
3249
3250        assert_eq!(out.next().await.unwrap(), (2, 204));
3251    }
3252
3253    #[cfg(feature = "deploy")]
3254    #[tokio::test]
3255    async fn reduce_watermark_bounded() {
3256        let mut deployment = Deployment::new();
3257
3258        let mut flow = FlowBuilder::new();
3259        let node = flow.process::<()>();
3260        let external = flow.external::<()>();
3261
3262        let node_tick = node.tick();
3263        let watermark = node_tick.singleton(q!(2));
3264
3265        let sum = node
3266            .source_iter(q!([(0, 100), (1, 101), (2, 102), (2, 102)]))
3267            .into_keyed()
3268            .reduce_watermark(
3269                watermark,
3270                q!(|acc, v| {
3271                    *acc += v;
3272                }),
3273            )
3274            .entries()
3275            .send_bincode_external(&external);
3276
3277        let nodes = flow
3278            .with_process(&node, deployment.Localhost())
3279            .with_external(&external, deployment.Localhost())
3280            .deploy(&mut deployment);
3281
3282        deployment.deploy().await.unwrap();
3283
3284        let mut out = nodes.connect(sum).await;
3285
3286        deployment.start().await.unwrap();
3287
3288        assert_eq!(out.next().await.unwrap(), (2, 204));
3289    }
3290
3291    #[cfg(feature = "deploy")]
3292    #[tokio::test]
3293    async fn reduce_watermark_garbage_collect() {
3294        let mut deployment = Deployment::new();
3295
3296        let mut flow = FlowBuilder::new();
3297        let node = flow.process::<()>();
3298        let external = flow.external::<()>();
3299        let (tick_send, tick_trigger) =
3300            node.source_external_bincode::<_, _, _, ExactlyOnce>(&external);
3301
3302        let node_tick = node.tick();
3303        let (watermark_complete_cycle, watermark) =
3304            node_tick.cycle_with_initial(node_tick.singleton(q!(2)));
3305        let next_watermark = watermark.clone().map(q!(|v| v + 1));
3306        watermark_complete_cycle.complete_next_tick(next_watermark);
3307
3308        let tick_triggered_input = node_tick
3309            .singleton(q!((3, 103)))
3310            .into_stream()
3311            .filter_if(
3312                tick_trigger
3313                    .clone()
3314                    .batch(&node_tick, nondet!(/** test */))
3315                    .first()
3316                    .is_some(),
3317            )
3318            .all_ticks();
3319
3320        // Snapshot the top-level reduce output in a *distinct* tick from `node_tick` (which
3321        // supplies the watermark and tick-triggered inputs to the reduce), so we don't re-enter
3322        // `node_tick` — a value that already exited that tick coming back into it is not
3323        // representable as a DFIR loop.
3324        let snapshot_tick = node.tick();
3325        let sum = node
3326            .source_stream(q!(tokio_stream::iter([
3327                (0, 100),
3328                (1, 101),
3329                (2, 102),
3330                (2, 102)
3331            ])))
3332            .merge_unordered(tick_triggered_input)
3333            .into_keyed()
3334            .reduce_watermark(
3335                watermark,
3336                q!(
3337                    |acc, v| {
3338                        *acc += v;
3339                    },
3340                    commutative = manual_proof!(/** integer addition is commutative */)
3341                ),
3342            )
3343            .snapshot(&snapshot_tick, nondet!(/** test */))
3344            .entries()
3345            .all_ticks()
3346            .send_bincode_external(&external);
3347
3348        let nodes = flow
3349            .with_default_optimize()
3350            .with_process(&node, deployment.Localhost())
3351            .with_external(&external, deployment.Localhost())
3352            .deploy(&mut deployment);
3353
3354        deployment.deploy().await.unwrap();
3355
3356        let mut tick_send = nodes.connect(tick_send).await;
3357        let mut out_recv = nodes.connect(sum).await;
3358
3359        deployment.start().await.unwrap();
3360
3361        assert_eq!(out_recv.next().await.unwrap(), (2, 204));
3362
3363        tick_send.send(()).await.unwrap();
3364
3365        assert_eq!(out_recv.next().await.unwrap(), (3, 103));
3366    }
3367
3368    #[cfg(feature = "sim")]
3369    #[test]
3370    #[should_panic]
3371    fn sim_batch_nondet_size() {
3372        let mut flow = FlowBuilder::new();
3373        let node = flow.process::<()>();
3374
3375        let input = node.source_iter(q!([(1, 1), (1, 2), (2, 3)])).into_keyed();
3376
3377        let tick = node.tick();
3378        let out_recv = input
3379            .batch(&tick, nondet!(/** test */))
3380            .fold(q!(|| vec![]), q!(|acc, v| acc.push(v)))
3381            .entries()
3382            .all_ticks()
3383            .sim_output();
3384
3385        flow.sim().exhaustive(async || {
3386            out_recv
3387                .assert_yields_only_unordered([(1, vec![1, 2])])
3388                .await;
3389        });
3390    }
3391
3392    #[cfg(feature = "sim")]
3393    #[test]
3394    fn sim_batch_preserves_group_order() {
3395        let mut flow = FlowBuilder::new();
3396        let node = flow.process::<()>();
3397
3398        let input = node.source_iter(q!([(1, 1), (1, 2), (2, 3)])).into_keyed();
3399
3400        let tick = node.tick();
3401        let out_recv = input
3402            .batch(&tick, nondet!(/** test */))
3403            .all_ticks()
3404            .fold_early_stop(
3405                q!(|| 0),
3406                q!(|acc, v| {
3407                    *acc = std::cmp::max(v, *acc);
3408                    *acc >= 2
3409                }),
3410            )
3411            .entries()
3412            .sim_output();
3413
3414        let instances = flow.sim().exhaustive(async || {
3415            out_recv
3416                .assert_yields_only_unordered([(1, 2), (2, 3)])
3417                .await;
3418        });
3419
3420        assert_eq!(instances, 8);
3421        // (final quiescence checks are free here: the simulation settles deterministically
3422        // once all expected messages have been observed, so no extra instances are explored)
3423        // - three cases: all three in a separate tick (pick where (2, 3) is)
3424        // - two cases: (1, 1) and (1, 2) together, (2, 3) before or after
3425        // - two cases: (1, 1) and (1, 2) separate, (2, 3) grouped with one of them
3426        // - one case: all three together
3427    }
3428
3429    #[cfg(feature = "sim")]
3430    #[test]
3431    fn sim_batch_unordered_shuffles() {
3432        let mut flow = FlowBuilder::new();
3433        let node = flow.process::<()>();
3434
3435        let input = node
3436            .source_iter(q!([(1, 1), (1, 2), (2, 3)]))
3437            .into_keyed()
3438            .weaken_ordering::<NoOrder>();
3439
3440        let tick = node.tick();
3441        let out_recv = input
3442            .batch(&tick, nondet!(/** test */))
3443            .all_ticks()
3444            .entries()
3445            .sim_output();
3446
3447        let instances = flow.sim().exhaustive(async || {
3448            out_recv
3449                .assert_yields_only_unordered([(1, 1), (1, 2), (2, 3)])
3450                .await;
3451        });
3452
3453        assert_eq!(instances, 13);
3454        // - 6 (3 * 2) cases: all three in a separate tick (pick where (2, 3) is), and order of (1, 1), (1, 2)
3455        // - two cases: (1, 1) and (1, 2) together, (2, 3) before or after (order of (1, 1), (1, 2) doesn't matter because batched is still unordered)
3456        // - 4 (2 * 2) cases: (1, 1) and (1, 2) separate, (2, 3) grouped with one of them, and order of (1, 1), (1, 2)
3457        // - one case: all three together (order of (1, 1), (1, 2) doesn't matter because batched is still unordered)
3458    }
3459
3460    #[cfg(feature = "sim")]
3461    #[test]
3462    #[should_panic]
3463    fn sim_observe_order_batched() {
3464        let mut flow = FlowBuilder::new();
3465        let node = flow.process::<()>();
3466
3467        let (port, input) = node.sim_input::<_, NoOrder, _>();
3468
3469        let tick = node.tick();
3470        let batch = input.into_keyed().batch(&tick, nondet!(/** test */));
3471        let out_recv = batch
3472            .assume_ordering::<TotalOrder>(nondet!(/** test */))
3473            .all_ticks()
3474            .first()
3475            .entries()
3476            .sim_output();
3477
3478        flow.sim().exhaustive(async || {
3479            port.send_many_unordered([(1, 1), (1, 2), (2, 1), (2, 2)]);
3480            out_recv
3481                .assert_yields_only_unordered([(1, 1), (2, 1)])
3482                .await; // fails with assume_ordering
3483        });
3484    }
3485
3486    #[cfg(feature = "sim")]
3487    #[test]
3488    fn sim_observe_order_batched_count() {
3489        let mut flow = FlowBuilder::new();
3490        let node = flow.process::<()>();
3491
3492        let (port, input) = node.sim_input::<_, NoOrder, _>();
3493
3494        let tick = node.tick();
3495        let batch = input.into_keyed().batch(&tick, nondet!(/** test */));
3496        let out_recv = batch
3497            .assume_ordering::<TotalOrder>(nondet!(/** test */))
3498            .all_ticks()
3499            .entries()
3500            .sim_output();
3501
3502        let instance_count = flow.sim().exhaustive(async || {
3503            port.send_many_unordered([(1, 1), (1, 2), (2, 1), (2, 2)]);
3504            let _ = out_recv.collect_sorted::<Vec<_>>().await;
3505        });
3506
3507        assert_eq!(instance_count, 104); // too complicated to enumerate here, but less than stream equivalent
3508    }
3509
3510    #[cfg(feature = "sim")]
3511    #[test]
3512    fn sim_top_level_assume_ordering() {
3513        use std::collections::HashMap;
3514
3515        let mut flow = FlowBuilder::new();
3516        let node = flow.process::<()>();
3517
3518        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3519
3520        let out_recv = input
3521            .into_keyed()
3522            .assume_ordering::<TotalOrder>(nondet!(/** test */))
3523            .fold_early_stop(
3524                q!(|| Vec::new()),
3525                q!(|acc, v| {
3526                    acc.push(v);
3527                    acc.len() >= 2
3528                }),
3529            )
3530            .entries()
3531            .sim_output();
3532
3533        let instance_count = flow.sim().exhaustive(async || {
3534            in_send.send_many_unordered([(1, 'a'), (1, 'b'), (2, 'c'), (2, 'd')]);
3535            let out: HashMap<_, _> = out_recv
3536                .collect_sorted::<Vec<_>>()
3537                .await
3538                .into_iter()
3539                .collect();
3540            // Each key accumulates its values; we get one entry per key
3541            assert_eq!(out.len(), 2);
3542        });
3543
3544        assert_eq!(instance_count, 24)
3545    }
3546
3547    #[cfg(feature = "sim")]
3548    #[test]
3549    fn sim_top_level_assume_ordering_cycle_back() {
3550        use std::collections::HashMap;
3551
3552        let mut flow = FlowBuilder::new();
3553        let node = flow.process::<()>();
3554        let node2 = flow.process::<()>();
3555
3556        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3557
3558        let (complete_cycle_back, cycle_back) =
3559            node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3560        let ordered = input
3561            .into_keyed()
3562            .merge_unordered(cycle_back)
3563            .assume_ordering::<TotalOrder>(nondet!(/** test */));
3564        complete_cycle_back.complete(
3565            ordered
3566                .clone()
3567                .map(q!(|v| v + 1))
3568                .filter(q!(|v| v % 2 == 1))
3569                .entries()
3570                .send(&node2, TCP.fail_stop().bincode())
3571                .send(&node, TCP.fail_stop().bincode())
3572                .into_keyed(),
3573        );
3574
3575        let out_recv = ordered
3576            .fold_early_stop(
3577                q!(|| Vec::new()),
3578                q!(|acc, v| {
3579                    acc.push(v);
3580                    acc.len() >= 2
3581                }),
3582            )
3583            .entries()
3584            .sim_output();
3585
3586        let mut saw = false;
3587        let instance_count = flow.sim().exhaustive(async || {
3588            // Send (1, 0) and (1, 2). 0+1=1 is odd so cycles back.
3589            // We want to see [0, 1] - the cycled back value interleaved
3590            in_send.send_many_unordered([(1, 0), (1, 2)]);
3591            let out: HashMap<_, _> = out_recv
3592                .collect_sorted::<Vec<_>>()
3593                .await
3594                .into_iter()
3595                .collect();
3596
3597            // We want to see an instance where key 1 gets: 0, then 1 (cycled back from 0+1)
3598            if let Some(values) = out.get(&1)
3599                && *values == vec![0, 1]
3600            {
3601                saw = true;
3602            }
3603        });
3604
3605        assert!(
3606            saw,
3607            "did not see an instance with key 1 having [0, 1] in order"
3608        );
3609        assert_eq!(instance_count, 6);
3610    }
3611
3612    #[cfg(feature = "sim")]
3613    #[test]
3614    fn sim_top_level_assume_ordering_cross_key_cycle() {
3615        use std::collections::HashMap;
3616
3617        // This test demonstrates why releasing one entry at a time is important:
3618        // When one key's observed order cycles back into a different key, we need
3619        // to be able to interleave the cycled-back entry with pending items for
3620        // that other key.
3621        let mut flow = FlowBuilder::new();
3622        let node = flow.process::<()>();
3623        let node2 = flow.process::<()>();
3624
3625        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3626
3627        let (complete_cycle_back, cycle_back) =
3628            node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3629        let ordered = input
3630            .into_keyed()
3631            .merge_unordered(cycle_back)
3632            .assume_ordering::<TotalOrder>(nondet!(/** test */));
3633
3634        // Cycle back: when we see (1, 10), emit (2, 100) to key 2
3635        complete_cycle_back.complete(
3636            ordered
3637                .clone()
3638                .filter(q!(|v| *v == 10))
3639                .map(q!(|_| 100))
3640                .entries()
3641                .map(q!(|(_, v)| (2, v))) // Change key from 1 to 2
3642                .send(&node2, TCP.fail_stop().bincode())
3643                .send(&node, TCP.fail_stop().bincode())
3644                .into_keyed(),
3645        );
3646
3647        let out_recv = ordered
3648            .fold_early_stop(
3649                q!(|| Vec::new()),
3650                q!(|acc, v| {
3651                    acc.push(v);
3652                    acc.len() >= 2
3653                }),
3654            )
3655            .entries()
3656            .sim_output();
3657
3658        // We want to see an instance where:
3659        // - (1, 10) is released first
3660        // - This causes (2, 100) to be cycled back
3661        // - (2, 100) is released BEFORE (2, 20) which was already pending
3662        let mut saw_cross_key_interleave = false;
3663        let instance_count = flow.sim().exhaustive(async || {
3664            // Send (1, 10), (1, 11) for key 1, and (2, 20), (2, 21) for key 2
3665            in_send.send_many_unordered([(1, 10), (1, 11), (2, 20), (2, 21)]);
3666            let out: HashMap<_, _> = out_recv
3667                .collect_sorted::<Vec<_>>()
3668                .await
3669                .into_iter()
3670                .collect();
3671
3672            // Check if we see the cross-key interleaving:
3673            // key 2 should have [100, 20] or [100, 21] - cycled back 100 before a pending item
3674            if let Some(values) = out.get(&2)
3675                && values.len() >= 2
3676                && values[0] == 100
3677            {
3678                saw_cross_key_interleave = true;
3679            }
3680        });
3681
3682        assert!(
3683            saw_cross_key_interleave,
3684            "did not see an instance where cycled-back 100 was released before pending items for key 2"
3685        );
3686        assert_eq!(instance_count, 60);
3687    }
3688
3689    #[cfg(feature = "sim")]
3690    #[test]
3691    fn sim_top_level_assume_ordering_cycle_back_tick() {
3692        use std::collections::HashMap;
3693
3694        let mut flow = FlowBuilder::new();
3695        let node = flow.process::<()>();
3696        let node2 = flow.process::<()>();
3697
3698        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3699
3700        let (complete_cycle_back, cycle_back) =
3701            node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3702        let ordered = input
3703            .into_keyed()
3704            .merge_unordered(cycle_back)
3705            .assume_ordering::<TotalOrder>(nondet!(/** test */));
3706        complete_cycle_back.complete(
3707            ordered
3708                .clone()
3709                .batch(&node.tick(), nondet!(/** test */))
3710                .all_ticks()
3711                .map(q!(|v| v + 1))
3712                .filter(q!(|v| v % 2 == 1))
3713                .entries()
3714                .send(&node2, TCP.fail_stop().bincode())
3715                .send(&node, TCP.fail_stop().bincode())
3716                .into_keyed(),
3717        );
3718
3719        let out_recv = ordered
3720            .fold_early_stop(
3721                q!(|| Vec::new()),
3722                q!(|acc, v| {
3723                    acc.push(v);
3724                    acc.len() >= 2
3725                }),
3726            )
3727            .entries()
3728            .sim_output();
3729
3730        let mut saw = false;
3731        let instance_count = flow.sim().exhaustive(async || {
3732            in_send.send_many_unordered([(1, 0), (1, 2)]);
3733            let out: HashMap<_, _> = out_recv
3734                .collect_sorted::<Vec<_>>()
3735                .await
3736                .into_iter()
3737                .collect();
3738
3739            if let Some(values) = out.get(&1)
3740                && *values == vec![0, 1]
3741            {
3742                saw = true;
3743            }
3744        });
3745
3746        assert!(
3747            saw,
3748            "did not see an instance with key 1 having [0, 1] in order"
3749        );
3750        assert_eq!(instance_count, 58);
3751    }
3752
3753    #[cfg(feature = "sim")]
3754    #[test]
3755    fn sim_entries_partially_ordered_bounded() {
3756        let mut flow = FlowBuilder::new();
3757        let node = flow.process::<()>();
3758
3759        let (port, input) = node.sim_input::<_, TotalOrder, _>();
3760
3761        let tick = node.tick();
3762        let batch = input.into_keyed().batch(&tick, nondet!(/** test */));
3763        let out_recv = batch
3764            .entries_partially_ordered(nondet!(/** test */))
3765            .all_ticks()
3766            .sim_output();
3767
3768        let instance_count = flow.sim().exhaustive(async || {
3769            port.send((1, 'a'));
3770            port.send((1, 'b'));
3771            port.send((2, 'c'));
3772            let _: Vec<(i32, char)> = out_recv.collect().await;
3773        });
3774
3775        assert_eq!(instance_count, 12);
3776    }
3777
3778    #[cfg(feature = "sim")]
3779    #[test]
3780    fn sim_entries_partially_ordered_top_level() {
3781        let mut flow = FlowBuilder::new();
3782        let node = flow.process::<()>();
3783
3784        let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3785
3786        let out_recv = input
3787            .into_keyed()
3788            .entries_partially_ordered(nondet!(/** test */))
3789            .sim_output();
3790
3791        let instance_count = flow.sim().exhaustive(async || {
3792            in_send.send((1, 'a'));
3793            in_send.send((1, 'b'));
3794            in_send.send((2, 'c'));
3795            let _: Vec<(i32, char)> = out_recv.collect().await;
3796        });
3797
3798        assert_eq!(instance_count, 3);
3799    }
3800
3801    #[cfg(feature = "sim")]
3802    #[test]
3803    fn sim_entries_partially_ordered_cycle_back() {
3804        let mut flow = FlowBuilder::new();
3805        let node = flow.process::<()>();
3806        let node2 = flow.process::<()>();
3807
3808        let (in_send, input) = node.sim_input::<_, NoOrder, _>();
3809
3810        let (complete_cycle_back, cycle_back) =
3811            node.forward_ref::<super::KeyedStream<_, _, _, _, NoOrder>>();
3812        let ordered = input
3813            .into_keyed()
3814            .merge_unordered(cycle_back)
3815            .assume_ordering::<TotalOrder>(nondet!(/** test */));
3816
3817        let flat = ordered
3818            .clone()
3819            .entries_partially_ordered(nondet!(/** test */));
3820
3821        complete_cycle_back.complete(
3822            flat.clone()
3823                .map(q!(|(k, v): (i32, i32)| (k, v + 1)))
3824                .filter(q!(|(_, v)| *v % 2 == 1))
3825                .send(&node2, TCP.fail_stop().bincode())
3826                .send(&node, TCP.fail_stop().bincode())
3827                .into_keyed(),
3828        );
3829
3830        let out_recv = flat.sim_output();
3831
3832        let mut saw = false;
3833        let instance_count = flow.sim().exhaustive(async || {
3834            // Send (1, 0) and (1, 2). 0+1=1 is odd so cycles back as (1, 1).
3835            // We want to see (1, 1) before (1, 2) - the cycled back value beats the pending one
3836            in_send.send_many_unordered([(1, 0), (1, 2)]);
3837            let results: Vec<(i32, i32)> = out_recv.collect().await;
3838
3839            let pos_1 = results.iter().position(|v| *v == (1, 1));
3840            let pos_2 = results.iter().position(|v| *v == (1, 2));
3841            if let (Some(p1), Some(p2)) = (pos_1, pos_2)
3842                && p1 < p2
3843            {
3844                saw = true;
3845            }
3846        });
3847
3848        assert!(saw, "did not see an instance with (1, 1) before (1, 2)");
3849        assert_eq!(instance_count, 28);
3850    }
3851
3852    /// Tests that `merge_ordered` on a keyed stream explores every valid
3853    /// interleaving within a shared key while always preserving per-input
3854    /// order.
3855    #[cfg(feature = "sim")]
3856    #[test]
3857    fn sim_keyed_merge_ordered() {
3858        let mut flow = FlowBuilder::new();
3859        let node = flow.process::<()>();
3860
3861        let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3862        let (in_send2, input2) = node.sim_input::<_, TotalOrder, _>();
3863
3864        let out_recv = input
3865            .into_keyed()
3866            .merge_ordered(input2.into_keyed(), nondet!(/** test */))
3867            .entries_partially_ordered(nondet!(/** test */))
3868            .sim_output();
3869
3870        let mut saw_first = false;
3871        let mut saw_interleaved = false;
3872        let mut saw_second_first = false;
3873        let instances = flow.sim().exhaustive(async || {
3874            in_send.send((1, 'a'));
3875            in_send.send((1, 'b'));
3876            in_send2.send((1, 'c'));
3877
3878            let out: Vec<(i32, char)> = out_recv.collect().await;
3879            let key1: Vec<char> = out
3880                .iter()
3881                .filter(|(k, _)| *k == 1)
3882                .map(|(_, v)| *v)
3883                .collect();
3884
3885            // Within-group order for the first input must always be preserved.
3886            let first_order: Vec<char> = key1
3887                .iter()
3888                .filter(|c| **c == 'a' || **c == 'b')
3889                .copied()
3890                .collect();
3891            assert_eq!(
3892                first_order,
3893                vec!['a', 'b'],
3894                "within-group order violated: {:?}",
3895                out
3896            );
3897
3898            match key1.as_slice() {
3899                ['a', 'b', 'c'] => saw_first = true,
3900                ['a', 'c', 'b'] => saw_interleaved = true,
3901                ['c', 'a', 'b'] => saw_second_first = true,
3902                other => panic!("unexpected interleaving: {:?}", other),
3903            }
3904        });
3905
3906        assert!(saw_first, "did not observe [a, b, c]");
3907        assert!(saw_interleaved, "did not observe [a, c, b]");
3908        assert!(saw_second_first, "did not observe [c, a, b]");
3909        assert_eq!(instances, 15);
3910    }
3911
3912    /// Tests that `merge_ordered` on a keyed stream interleaves each group
3913    /// *independently*. It must be possible to observe, in the same execution,
3914    /// key `10` taking its second-input value before its first-input value
3915    /// while key `20` does the opposite. A merge that treated the two inputs
3916    /// as a single totally-ordered sequence could not produce this combination.
3917    #[cfg(feature = "sim")]
3918    #[test]
3919    fn sim_keyed_merge_ordered_independent_keys() {
3920        let mut flow = FlowBuilder::new();
3921        let node = flow.process::<()>();
3922
3923        let (in_send, input) = node.sim_input::<_, TotalOrder, _>();
3924        let (in_send2, input2) = node.sim_input::<_, TotalOrder, _>();
3925
3926        let out_recv = input
3927            .into_keyed()
3928            .merge_ordered(input2.into_keyed(), nondet!(/** test */))
3929            .entries_partially_ordered(nondet!(/** test */))
3930            .sim_output();
3931
3932        let mut saw_independent = false;
3933        let instances = flow.sim().exhaustive(async || {
3934            // First input: key 10 -> [1], key 20 -> [2].
3935            in_send.send((10, 1));
3936            in_send.send((20, 2));
3937            // Second input: key 10 -> [4], key 20 -> [3].
3938            in_send2.send((10, 4));
3939            in_send2.send((20, 3));
3940
3941            let out: Vec<(i32, i32)> = out_recv.collect().await;
3942            let key10: Vec<i32> = out
3943                .iter()
3944                .filter(|(k, _)| *k == 10)
3945                .map(|(_, v)| *v)
3946                .collect();
3947            let key20: Vec<i32> = out
3948                .iter()
3949                .filter(|(k, _)| *k == 20)
3950                .map(|(_, v)| *v)
3951                .collect();
3952
3953            // Within-input order must be preserved within each key (each key has
3954            // a single value per input here, so only the multiset is checked).
3955            let mut s10 = key10.clone();
3956            s10.sort();
3957            assert_eq!(s10, vec![1, 4], "unexpected values for key 10: {:?}", out);
3958            let mut s20 = key20.clone();
3959            s20.sort();
3960            assert_eq!(s20, vec![2, 3], "unexpected values for key 20: {:?}", out);
3961
3962            // key 10: second-input value (4) before first-input value (1).
3963            // key 20: first-input value (2) before second-input value (3).
3964            if key10 == vec![4, 1] && key20 == vec![2, 3] {
3965                saw_independent = true;
3966            }
3967        });
3968
3969        assert!(
3970            saw_independent,
3971            "did not observe per-key-independent interleaving"
3972        );
3973        assert_eq!(instances, 1120);
3974    }
3975}