Skip to main content

hydro_lang/live_collections/
optional.rs

1//! Definitions for the [`Optional`] live collection.
2
3use std::cell::RefCell;
4use std::marker::PhantomData;
5use std::ops::Deref;
6use std::rc::Rc;
7
8use sealed::sealed;
9use stageleft::{IntoQuotedMut, QuotedWithContext, q};
10use syn::parse_quote;
11
12use super::OperatorContext;
13use super::boundedness::{Bounded, Boundedness, IsBounded, Unbounded};
14use super::singleton::Singleton;
15use super::stream::{AtLeastOnce, ExactlyOnce, NoOrder, Stream, TotalOrder};
16use crate::compile::builder::{CycleId, FlowState};
17use crate::compile::ir::{
18    CollectionKind, HydroIrOpMetadata, HydroNode, HydroRoot, OptionalBoundKind, SharedNode,
19};
20#[cfg(stageleft_runtime)]
21use crate::forward_handle::{CycleCollection, CycleCollectionWithInitial, ReceiverComplete};
22use crate::forward_handle::{ForwardRef, TickCycle};
23use crate::live_collections::singleton::SingletonBound;
24#[cfg(feature = "tokio")]
25use crate::location::TopLevel;
26#[cfg(stageleft_runtime)]
27use crate::location::dynamic::{DynLocation, LocationId};
28use crate::location::tick::{Atomic, DeferTick};
29use crate::location::{Location, Tick, check_matching_location};
30use crate::nondet::{NonDet, nondet};
31use crate::prelude::KeyedSingleton;
32use crate::properties::{StreamMapFuncAlgebra, ValidMutCommutativityFor, ValidMutIdempotenceFor};
33
34/// A marker trait indicating the boundedness of an [`Optional`].
35///
36/// In addition to [`Bounded`] (immutable) and [`Unbounded`] (arbitrarily mutable, including
37/// becoming null again), this also includes additional variants that constrain how the
38/// optional's *presence* (whether it is null) and value evolve over time.
39///
40/// The currently defined variants form a hierarchy of increasing strength; variants other than
41/// [`Bounded`] erase to [`Unbounded`]:
42/// - [`Unbounded`]: the optional may become null and non-null arbitrarily, with an arbitrary
43///   value whenever it is non-null.
44/// - [`InitNone`]: the optional starts null, but once it becomes non-null it stays
45///   non-null forever; the non-null value may still change arbitrarily.
46pub trait OptionalBound {
47    /// The [`Boundedness`] that this [`Optional`] would be erased to.
48    type UnderlyingBound: Boundedness;
49
50    /// Returns the [`OptionalBoundKind`] corresponding to this type.
51    fn bound_kind() -> OptionalBoundKind;
52}
53
54impl OptionalBound for Unbounded {
55    type UnderlyingBound = Unbounded;
56
57    fn bound_kind() -> OptionalBoundKind {
58        OptionalBoundKind::Unbounded
59    }
60}
61
62impl OptionalBound for Bounded {
63    type UnderlyingBound = Bounded;
64
65    fn bound_kind() -> OptionalBoundKind {
66        OptionalBoundKind::Bounded
67    }
68}
69
70/// Marks that the [`Optional`] is null only initially: once it becomes non-null it will remain
71/// non-null forever, although the non-null value may still change arbitrarily over time.
72///
73/// This erases to [`Unbounded`], since the value (when present) is still asynchronously changing.
74pub enum InitNone {}
75
76impl OptionalBound for InitNone {
77    type UnderlyingBound = Unbounded;
78
79    fn bound_kind() -> OptionalBoundKind {
80        OptionalBoundKind::InitNone
81    }
82}
83
84#[sealed]
85#[diagnostic::on_unimplemented(
86    message = "The optional must be null-only-initially (`InitNone`) or bounded (`Bounded`), but has bound `{Self}`. Strengthen the guarantee upstream or consider a different API.",
87    label = "required here",
88    note = "To intentionally process a non-deterministic snapshot or batch, you may want to use a `sliced!` region. This introduces non-determinism so avoid unless necessary."
89)]
90/// Marker trait that is implemented for [`OptionalBound`] types that are null only initially:
91/// once the optional becomes non-null it remains non-null (or it is [`Bounded`]).
92pub trait IsInitNone: OptionalBound {}
93
94#[sealed]
95#[diagnostic::do_not_recommend]
96impl IsInitNone for InitNone {}
97
98#[sealed]
99#[diagnostic::do_not_recommend]
100impl<B: IsBounded> IsInitNone for B {}
101
102/// A *nullable* Rust value that can asynchronously change over time.
103///
104/// Optionals are the live collection equivalent of [`Option`]. If the optional is [`Bounded`],
105/// the value is frozen and will not change. But if it is [`Unbounded`], the value will
106/// asynchronously change over time, including becoming present of uninhabited.
107///
108/// Optionals are used in many of the same places as [`Singleton`], but when the value may be
109/// nullable. For example, the first element of a [`Stream`] is exposed as an [`Optional`].
110///
111/// Type Parameters:
112/// - `Type`: the type of the value in this optional (when it is not null)
113/// - `Loc`: the [`Location`] where the optional is materialized
114/// - `Bound`: tracks whether the value is [`Bounded`] (fixed) or [`Unbounded`] (changing
115///   asynchronously) or something in between (see [`OptionalBoundKind`]).
116pub struct Optional<Type, Loc, Bound: OptionalBound> {
117    pub(crate) location: Loc,
118    pub(crate) ir_node: Rc<RefCell<HydroNode>>,
119    pub(crate) flow_state: FlowState,
120
121    _phantom: PhantomData<(Type, Loc, Bound)>,
122}
123
124impl<T, L, B: OptionalBound> Drop for Optional<T, L, B> {
125    fn drop(&mut self) {
126        let ir_node = self.ir_node.replace(HydroNode::Placeholder);
127        if !matches!(ir_node, HydroNode::Placeholder) && !ir_node.is_shared_with_others() {
128            self.flow_state.borrow_mut().try_push_root(HydroRoot::Null {
129                input: Box::new(ir_node),
130                op_metadata: HydroIrOpMetadata::new(),
131            });
132        }
133    }
134}
135
136impl<'a, T, L> From<Optional<T, L, Bounded>> for Optional<T, L, Unbounded>
137where
138    T: Clone,
139    L: Location<'a>,
140{
141    fn from(value: Optional<T, L, Bounded>) -> Self {
142        let tick = value.location().tick();
143        value.clone_into_tick(&tick).latest()
144    }
145}
146
147impl<'a, T, L> From<Optional<T, L, InitNone>> for Optional<T, L, Unbounded>
148where
149    L: Location<'a>,
150{
151    fn from(value: Optional<T, L, InitNone>) -> Self {
152        value.ignore_init_none()
153    }
154}
155
156impl<'a, T, L> DeferTick for Optional<T, Tick<L>, Bounded>
157where
158    L: Location<'a>,
159{
160    fn defer_tick(self) -> Self {
161        Optional::defer_tick(self)
162    }
163}
164
165impl<'a, T, L> CycleCollection<'a, TickCycle> for Optional<T, Tick<L>, Bounded>
166where
167    L: Location<'a>,
168{
169    type Location = Tick<L>;
170
171    fn create_source(cycle_id: CycleId, location: Tick<L>) -> Self {
172        Optional::new(
173            location.clone(),
174            HydroNode::CycleSource {
175                cycle_id,
176                metadata: location.new_node_metadata(Self::collection_kind()),
177            },
178        )
179    }
180}
181
182impl<'a, T, L> CycleCollectionWithInitial<'a, TickCycle> for Optional<T, Tick<L>, Bounded>
183where
184    L: Location<'a>,
185{
186    type Location = Tick<L>;
187
188    fn location(&self) -> &Self::Location {
189        self.location()
190    }
191
192    fn create_source_with_initial(cycle_id: CycleId, initial: Self, location: Tick<L>) -> Self {
193        let from_previous_tick: Optional<T, Tick<L>, Bounded> = Optional::new(
194            location.clone(),
195            HydroNode::DeferTick {
196                input: Box::new(HydroNode::CycleSource {
197                    cycle_id,
198                    metadata: location.new_node_metadata(Self::collection_kind()),
199                }),
200                metadata: location
201                    .new_node_metadata(Optional::<T, Tick<L>, Bounded>::collection_kind()),
202            },
203        );
204
205        from_previous_tick.or(initial.filter_if(location.optional_first_tick(q!(())).is_some()))
206    }
207}
208
209impl<'a, T, L> ReceiverComplete<'a, TickCycle> for Optional<T, Tick<L>, Bounded>
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, T, L, B: OptionalBound> CycleCollection<'a, ForwardRef> for Optional<T, L, B>
231where
232    L: Location<'a>,
233{
234    type Location = L;
235
236    fn create_source(cycle_id: CycleId, location: L) -> Self {
237        Optional::new(
238            location.clone(),
239            HydroNode::CycleSource {
240                cycle_id,
241                metadata: location.new_node_metadata(Self::collection_kind()),
242            },
243        )
244    }
245}
246
247impl<'a, T, L, B: OptionalBound> ReceiverComplete<'a, ForwardRef> for Optional<T, L, B>
248where
249    L: Location<'a>,
250{
251    fn complete(self, cycle_id: CycleId, expected_location: LocationId) {
252        assert_eq!(
253            Location::id(&self.location),
254            expected_location,
255            "locations do not match"
256        );
257        self.location
258            .flow_state()
259            .borrow_mut()
260            .push_root(HydroRoot::CycleSink {
261                cycle_id,
262                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
263                op_metadata: HydroIrOpMetadata::new(),
264            });
265    }
266}
267
268impl<'a, T, L, B: SingletonBound> From<Singleton<T, L, B>> for Optional<T, L, B::UnderlyingBound>
269where
270    L: Location<'a>,
271{
272    fn from(singleton: Singleton<T, L, B>) -> Self {
273        Optional::new(
274            singleton.location.clone(),
275            HydroNode::Cast {
276                inner: Box::new(singleton.ir_node.replace(HydroNode::Placeholder)),
277                metadata: singleton
278                    .location
279                    .new_node_metadata(Self::collection_kind()),
280            },
281        )
282    }
283}
284
285#[cfg(stageleft_runtime)]
286pub(super) fn zip_inside_tick<'a, T, O, L: Location<'a>, B: Boundedness>(
287    me: Optional<T, L, B>,
288    other: Optional<O, L, B>,
289) -> Optional<(T, O), L, B> {
290    check_matching_location(&me.location, &other.location);
291
292    Optional::new(
293        me.location.clone(),
294        HydroNode::CrossSingleton {
295            left: Box::new(me.ir_node.replace(HydroNode::Placeholder)),
296            right: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
297            metadata: me
298                .location
299                .new_node_metadata(Optional::<(T, O), L, B>::collection_kind()),
300        },
301    )
302}
303
304#[cfg(stageleft_runtime)]
305fn or_inside_tick<'a, T, L: Location<'a>, B: Boundedness>(
306    me: Optional<T, L, B>,
307    other: Optional<T, L, B>,
308) -> Optional<T, L, B> {
309    check_matching_location(&me.location, &other.location);
310
311    Optional::new(
312        me.location.clone(),
313        HydroNode::ChainFirst {
314            first: Box::new(me.ir_node.replace(HydroNode::Placeholder)),
315            second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
316            metadata: me
317                .location
318                .new_node_metadata(Optional::<T, L, B>::collection_kind()),
319        },
320    )
321}
322
323impl<'a, T, L, B: OptionalBound> Clone for Optional<T, L, B>
324where
325    T: Clone,
326    L: Location<'a>,
327{
328    fn clone(&self) -> Self {
329        if !matches!(self.ir_node.borrow().deref(), HydroNode::Tee { .. }) {
330            let orig_ir_node = self.ir_node.replace(HydroNode::Placeholder);
331            *self.ir_node.borrow_mut() = HydroNode::Tee {
332                inner: SharedNode(Rc::new(RefCell::new(orig_ir_node))),
333                metadata: self.location.new_node_metadata(Self::collection_kind()),
334            };
335        }
336
337        if let HydroNode::Tee { inner, metadata } = self.ir_node.borrow().deref() {
338            Optional {
339                location: self.location.clone(),
340                flow_state: self.flow_state.clone(),
341                ir_node: super::tracked_ir_node(
342                    &self.flow_state,
343                    HydroNode::Tee {
344                        inner: SharedNode(inner.0.clone()),
345                        metadata: metadata.clone(),
346                    },
347                ),
348                _phantom: PhantomData,
349            }
350        } else {
351            unreachable!()
352        }
353    }
354}
355
356impl<'a, T, L, B: OptionalBound> Optional<T, L, B>
357where
358    L: Location<'a>,
359{
360    pub(crate) fn new(location: L, ir_node: HydroNode) -> Self {
361        debug_assert_eq!(ir_node.metadata().location_id, Location::id(&location));
362        debug_assert_eq!(ir_node.metadata().collection_kind, Self::collection_kind());
363        let flow_state = location.flow_state().clone();
364        let ir_node = super::tracked_ir_node(&flow_state, ir_node);
365        Optional {
366            location,
367            flow_state,
368            ir_node,
369            _phantom: PhantomData,
370        }
371    }
372
373    pub(crate) fn collection_kind() -> CollectionKind {
374        CollectionKind::Optional {
375            bound: <B as OptionalBound>::bound_kind(),
376            element_type: stageleft::quote_type::<T>().into(),
377        }
378    }
379
380    /// Returns the [`Location`] where this optional is being materialized.
381    pub fn location(&self) -> &L {
382        &self.location
383    }
384
385    /// Creates a shared reference handle to this optional that can be captured inside `q!()`
386    /// closures. The handle resolves to `&Option<T>` at runtime.
387    ///
388    /// The optional must be bounded, otherwise reading it would be non-deterministic.
389    /// The handle can only be captured in closures passed to operators on collections at
390    /// the same location with **matching boundedness**; capturing it in a closure over an
391    /// unbounded collection is rejected at compile time.
392    pub fn by_ref(&self) -> crate::handoff_ref::OptionalRef<'a, '_, T, L, B>
393    where
394        B: IsBounded,
395    {
396        crate::handoff_ref::OptionalRef::new(&self.ir_node)
397    }
398
399    /// Returns a mutable reference handle to this optional that can be captured inside `q!()`
400    /// closures. The handle resolves to `&mut Option<T>` at runtime.
401    pub fn by_mut(&self) -> crate::handoff_ref::OptionalMut<'a, '_, T, L, B>
402    where
403        B: IsBounded,
404    {
405        crate::handoff_ref::OptionalMut::new(&self.ir_node)
406    }
407
408    /// Weakens the consistency of this live collection to not guarantee any consistency across
409    /// cluster members (if this collection is on a cluster).
410    pub fn weaken_consistency(self) -> Optional<T, L::DropConsistency, B>
411    where
412        L: Location<'a>,
413    {
414        if L::consistency()
415            .is_none_or(|c| c == crate::location::dynamic::ClusterConsistency::NoConsistency)
416        {
417            // already no consistency
418            Optional::new(
419                self.location.drop_consistency(),
420                self.ir_node.replace(HydroNode::Placeholder),
421            )
422        } else {
423            Optional::new(
424                self.location.drop_consistency(),
425                HydroNode::Cast {
426                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
427                    metadata: self
428                        .location
429                        .clone()
430                        .drop_consistency()
431                        .new_node_metadata(Optional::<T, L::DropConsistency, B>::collection_kind()),
432                },
433            )
434        }
435    }
436
437    /// Casts this live collection to have the consistency guarantees specified in the given
438    /// location type parameter. The developer must ensure that the strengthened consistency
439    /// is actually guaranteed, via the proof field (see [`crate::prelude::manual_proof`]).
440    pub fn assert_has_consistency_of<L2: Location<'a, DropConsistency = L::DropConsistency>>(
441        self,
442        _proof: impl crate::properties::ConsistencyProof,
443    ) -> Optional<T, L2, B>
444    where
445        L: Location<'a>,
446    {
447        if L::consistency() == L2::consistency() {
448            Optional::new(
449                self.location.with_consistency_of(),
450                self.ir_node.replace(HydroNode::Placeholder),
451            )
452        } else {
453            Optional::new(
454                self.location.with_consistency_of(),
455                HydroNode::AssertIsConsistent {
456                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
457                    trusted: false,
458                    metadata: self
459                        .location
460                        .clone()
461                        .with_consistency_of::<L2>()
462                        .new_node_metadata(Optional::<T, L2, B>::collection_kind()),
463                },
464            )
465        }
466    }
467
468    /// Transforms the optional value by applying a function `f` to it,
469    /// continuously as the input is updated.
470    ///
471    /// Whenever the optional is empty, the output optional is also empty.
472    ///
473    /// # Example
474    /// ```rust
475    /// # #[cfg(feature = "deploy")] {
476    /// # use hydro_lang::prelude::*;
477    /// # use futures::StreamExt;
478    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
479    /// let tick = process.tick();
480    /// let optional = tick.optional_first_tick(q!(1));
481    /// optional.map(q!(|v| v + 1)).all_ticks()
482    /// # }, |mut stream| async move {
483    /// // 2
484    /// # assert_eq!(stream.next().await.unwrap(), 2);
485    /// # }));
486    /// # }
487    /// ```
488    pub fn map<U, F>(
489        self,
490        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B::UnderlyingBound>>,
491    ) -> Optional<U, L, B>
492    where
493        F: Fn(T) -> U + 'a,
494    {
495        let f = f
496            .splice_fn1_ctx(&OperatorContext::<L, B::UnderlyingBound>::new(
497                &self.location,
498            ))
499            .into();
500        Optional::new(
501            self.location.clone(),
502            HydroNode::Map {
503                f,
504                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
505                metadata: self
506                    .location
507                    .new_node_metadata(Optional::<U, L, B>::collection_kind()),
508            },
509        )
510    }
511
512    /// Transforms the optional value by applying a function `f` to it and then flattening
513    /// the result into a stream, preserving the order of elements.
514    ///
515    /// If the optional is empty, the output stream is also empty. If the optional contains
516    /// a value, `f` is applied to produce an iterator, and all items from that iterator
517    /// are emitted in the output stream in deterministic order.
518    ///
519    /// The implementation of [`Iterator`] for the output type `I` must produce items in a
520    /// **deterministic** order. For example, `I` could be a `Vec`, but not a `HashSet`.
521    /// If the order is not deterministic, use [`Optional::flat_map_unordered`] instead.
522    ///
523    /// # Example
524    /// ```rust
525    /// # #[cfg(feature = "deploy")] {
526    /// # use hydro_lang::prelude::*;
527    /// # use futures::StreamExt;
528    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
529    /// let tick = process.tick();
530    /// let optional = tick.optional_first_tick(q!(vec![1, 2, 3]));
531    /// optional.flat_map_ordered(q!(|v| v)).all_ticks()
532    /// # }, |mut stream| async move {
533    /// // 1, 2, 3
534    /// # for w in vec![1, 2, 3] {
535    /// #     assert_eq!(stream.next().await.unwrap(), w);
536    /// # }
537    /// # }));
538    /// # }
539    /// ```
540    pub fn flat_map_ordered<U, I, F, C, Idemp, const WAS_MUT: bool>(
541        self,
542        f: impl IntoQuotedMut<
543            'a,
544            F,
545            OperatorContext<L, Bounded>,
546            StreamMapFuncAlgebra<T, Bounded, C, Idemp>,
547        >,
548    ) -> Stream<U, L, Bounded, TotalOrder, ExactlyOnce>
549    where
550        B: IsBounded,
551        I: IntoIterator<Item = U>,
552        F: FnMut(T) -> I + 'a,
553        C: ValidMutCommutativityFor<F, T, I, TotalOrder, WAS_MUT>,
554        Idemp: ValidMutIdempotenceFor<F, T, I, ExactlyOnce, WAS_MUT>,
555    {
556        self.into_stream().flat_map_ordered(f)
557    }
558
559    /// Like [`Optional::flat_map_ordered`], but allows the implementation of [`Iterator`]
560    /// for the output type `I` to produce items in any order.
561    ///
562    /// If the optional is empty, the output stream is also empty. If the optional contains
563    /// a value, `f` is applied to produce an iterator, and all items from that iterator
564    /// are emitted in the output stream in non-deterministic order.
565    ///
566    /// # Example
567    /// ```rust
568    /// # #[cfg(feature = "deploy")] {
569    /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
570    /// # use futures::StreamExt;
571    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
572    /// let tick = process.tick();
573    /// let optional = tick.optional_first_tick(q!(
574    ///     std::collections::HashSet::<i32>::from_iter(vec![1, 2, 3])
575    /// ));
576    /// optional.flat_map_unordered(q!(|v| v)).all_ticks()
577    /// # }, |mut stream| async move {
578    /// // 1, 2, 3, but in no particular order
579    /// # let mut results = Vec::new();
580    /// # for _ in 0..3 {
581    /// #     results.push(stream.next().await.unwrap());
582    /// # }
583    /// # results.sort();
584    /// # assert_eq!(results, vec![1, 2, 3]);
585    /// # }));
586    /// # }
587    /// ```
588    pub fn flat_map_unordered<U, I, F, C, Idemp, const WAS_MUT: bool>(
589        self,
590        f: impl IntoQuotedMut<
591            'a,
592            F,
593            OperatorContext<L, Bounded>,
594            StreamMapFuncAlgebra<T, Bounded, C, Idemp>,
595        >,
596    ) -> Stream<U, L, Bounded, NoOrder, ExactlyOnce>
597    where
598        B: IsBounded,
599        I: IntoIterator<Item = U>,
600        F: FnMut(T) -> I + 'a,
601        C: ValidMutCommutativityFor<F, T, I, TotalOrder, WAS_MUT>,
602        Idemp: ValidMutIdempotenceFor<F, T, I, ExactlyOnce, WAS_MUT>,
603    {
604        self.into_stream().flat_map_unordered(f)
605    }
606
607    /// Flattens the optional value into a stream, preserving the order of elements.
608    ///
609    /// If the optional is empty, the output stream is also empty. If the optional contains
610    /// a value that implements [`IntoIterator`], all items from that iterator are emitted
611    /// in the output stream in deterministic order.
612    ///
613    /// The implementation of [`Iterator`] for the element type `T` must produce items in a
614    /// **deterministic** order. For example, `T` could be a `Vec`, but not a `HashSet`.
615    /// If the order is not deterministic, use [`Optional::flatten_unordered`] instead.
616    ///
617    /// # Example
618    /// ```rust
619    /// # #[cfg(feature = "deploy")] {
620    /// # use hydro_lang::prelude::*;
621    /// # use futures::StreamExt;
622    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
623    /// let tick = process.tick();
624    /// let optional = tick.optional_first_tick(q!(vec![1, 2, 3]));
625    /// optional.flatten_ordered().all_ticks()
626    /// # }, |mut stream| async move {
627    /// // 1, 2, 3
628    /// # for w in vec![1, 2, 3] {
629    /// #     assert_eq!(stream.next().await.unwrap(), w);
630    /// # }
631    /// # }));
632    /// # }
633    /// ```
634    pub fn flatten_ordered<U>(self) -> Stream<U, L, Bounded, TotalOrder, ExactlyOnce>
635    where
636        B: IsBounded,
637        T: IntoIterator<Item = U>,
638    {
639        self.flat_map_ordered(q!(|v| v))
640    }
641
642    /// Like [`Optional::flatten_ordered`], but allows the implementation of [`Iterator`]
643    /// for the element type `T` to produce items in any order.
644    ///
645    /// If the optional is empty, the output stream is also empty. If the optional contains
646    /// a value that implements [`IntoIterator`], all items from that iterator are emitted
647    /// in the output stream in non-deterministic order.
648    ///
649    /// # Example
650    /// ```rust
651    /// # #[cfg(feature = "deploy")] {
652    /// # use hydro_lang::{prelude::*, live_collections::stream::{NoOrder, ExactlyOnce}};
653    /// # use futures::StreamExt;
654    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test::<_, _, _, NoOrder, ExactlyOnce>(|process| {
655    /// let tick = process.tick();
656    /// let optional = tick.optional_first_tick(q!(
657    ///     std::collections::HashSet::<i32>::from_iter(vec![1, 2, 3])
658    /// ));
659    /// optional.flatten_unordered().all_ticks()
660    /// # }, |mut stream| async move {
661    /// // 1, 2, 3, but in no particular order
662    /// # let mut results = Vec::new();
663    /// # for _ in 0..3 {
664    /// #     results.push(stream.next().await.unwrap());
665    /// # }
666    /// # results.sort();
667    /// # assert_eq!(results, vec![1, 2, 3]);
668    /// # }));
669    /// # }
670    /// ```
671    pub fn flatten_unordered<U>(self) -> Stream<U, L, Bounded, NoOrder, ExactlyOnce>
672    where
673        B: IsBounded,
674        T: IntoIterator<Item = U>,
675    {
676        self.flat_map_unordered(q!(|v| v))
677    }
678
679    /// Creates an optional containing only the value if it satisfies a predicate `f`.
680    ///
681    /// If the optional is empty, the output optional is also empty. If the optional contains
682    /// a value and the predicate returns `true`, the output optional contains the same value.
683    /// If the predicate returns `false`, the output optional is empty.
684    ///
685    /// The closure `f` receives a reference `&T` rather than an owned value `T` because filtering does
686    /// not modify or take ownership of the value. If you need to modify the value while filtering
687    /// use [`Optional::filter_map`] instead.
688    ///
689    /// # Example
690    /// ```rust
691    /// # #[cfg(feature = "deploy")] {
692    /// # use hydro_lang::prelude::*;
693    /// # use futures::StreamExt;
694    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
695    /// let tick = process.tick();
696    /// let optional = tick.optional_first_tick(q!(5));
697    /// optional.filter(q!(|&x| x > 3)).all_ticks()
698    /// # }, |mut stream| async move {
699    /// // 5
700    /// # assert_eq!(stream.next().await.unwrap(), 5);
701    /// # }));
702    /// # }
703    /// ```
704    pub fn filter<F>(
705        self,
706        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B::UnderlyingBound>>,
707    ) -> Optional<T, L, B::UnderlyingBound>
708    where
709        F: Fn(&T) -> bool + 'a,
710    {
711        let f = f
712            .splice_fn1_borrow_ctx(&OperatorContext::<L, B::UnderlyingBound>::new(
713                &self.location,
714            ))
715            .into();
716        Optional::new(
717            self.location.clone(),
718            HydroNode::Filter {
719                f,
720                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
721                metadata: self
722                    .location
723                    .new_node_metadata(Optional::<T, L, B::UnderlyingBound>::collection_kind()),
724            },
725        )
726    }
727
728    /// An operator that both filters and maps. It yields only the value if the supplied
729    /// closure `f` returns `Some(value)`.
730    ///
731    /// If the optional is empty, the output optional is also empty. If the optional contains
732    /// a value and the closure returns `Some(new_value)`, the output optional contains `new_value`.
733    /// If the closure returns `None`, the output optional is empty.
734    ///
735    /// # Example
736    /// ```rust
737    /// # #[cfg(feature = "deploy")] {
738    /// # use hydro_lang::prelude::*;
739    /// # use futures::StreamExt;
740    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
741    /// let tick = process.tick();
742    /// let optional = tick.optional_first_tick(q!("42"));
743    /// optional
744    ///     .filter_map(q!(|s| s.parse::<i32>().ok()))
745    ///     .all_ticks()
746    /// # }, |mut stream| async move {
747    /// // 42
748    /// # assert_eq!(stream.next().await.unwrap(), 42);
749    /// # }));
750    /// # }
751    /// ```
752    pub fn filter_map<U, F>(
753        self,
754        f: impl IntoQuotedMut<'a, F, OperatorContext<L, B::UnderlyingBound>>,
755    ) -> Optional<U, L, B::UnderlyingBound>
756    where
757        F: Fn(T) -> Option<U> + 'a,
758    {
759        let f = f
760            .splice_fn1_ctx(&OperatorContext::<L, B::UnderlyingBound>::new(
761                &self.location,
762            ))
763            .into();
764        Optional::new(
765            self.location.clone(),
766            HydroNode::FilterMap {
767                f,
768                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
769                metadata: self
770                    .location
771                    .new_node_metadata(Optional::<U, L, B::UnderlyingBound>::collection_kind()),
772            },
773        )
774    }
775
776    /// Combines this singleton with another [`Singleton`] or [`Optional`] by tupling their values.
777    ///
778    /// If the other value is a [`Optional`], the output will be non-null only if the argument is
779    /// non-null. This is useful for combining several pieces of state together.
780    ///
781    /// # Example
782    /// ```rust
783    /// # #[cfg(feature = "deploy")] {
784    /// # use hydro_lang::prelude::*;
785    /// # use futures::StreamExt;
786    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
787    /// let tick = process.tick();
788    /// let numbers = process
789    ///   .source_iter(q!(vec![123, 456, 789]))
790    ///   .batch(&tick, nondet!(/** test */));
791    /// let min = numbers.clone().min(); // Optional
792    /// let max = numbers.max(); // Optional
793    /// min.zip(max).all_ticks()
794    /// # }, |mut stream| async move {
795    /// // [(123, 789)]
796    /// # for w in vec![(123, 789)] {
797    /// #     assert_eq!(stream.next().await.unwrap(), w);
798    /// # }
799    /// # }));
800    /// # }
801    /// ```
802    pub fn zip<O>(self, other: impl Into<Optional<O, L, B>>) -> Optional<(T, O), L, B>
803    where
804        B: IsBounded,
805    {
806        let other: Optional<O, L, B> = other.into();
807        check_matching_location(&self.location, &other.location);
808
809        if L::is_top_level()
810            && let Some(tick) = self.location.try_tick()
811        {
812            let self_location = self.location().clone();
813            let out = zip_inside_tick(
814                self.snapshot(&tick, nondet!(/** eventually stabilizes */)),
815                other.snapshot(&tick, nondet!(/** eventually stabilizes */)),
816            )
817            .latest();
818
819            Optional::new(self_location, out.ir_node.replace(HydroNode::Placeholder))
820        } else {
821            zip_inside_tick(self, other)
822        }
823    }
824
825    /// Passes through `self` when it has a value, otherwise passes through `other`.
826    ///
827    /// Like [`Option::or`], this is helpful for defining a fallback for an [`Optional`], when the
828    /// fallback itself is an [`Optional`]. If the fallback is a [`Singleton`], you can use
829    /// [`Optional::unwrap_or`] to ensure that the output is always non-null.
830    ///
831    /// If the inputs are [`Unbounded`], the output will be asynchronously updated as the contents
832    /// of the inputs change (including to/from null states).
833    ///
834    /// # Example
835    /// ```rust
836    /// # #[cfg(feature = "deploy")] {
837    /// # use hydro_lang::prelude::*;
838    /// # use futures::StreamExt;
839    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
840    /// let tick = process.tick();
841    /// // ticks are lazy by default, forces the second tick to run
842    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
843    ///
844    /// let some_first_tick = tick.optional_first_tick(q!(123));
845    /// let some_second_tick = tick.optional_first_tick(q!(456)).defer_tick();
846    /// some_first_tick.or(some_second_tick).all_ticks()
847    /// # }, |mut stream| async move {
848    /// // [123 /* first tick */, 456 /* second tick */]
849    /// # for w in vec![123, 456] {
850    /// #     assert_eq!(stream.next().await.unwrap(), w);
851    /// # }
852    /// # }));
853    /// # }
854    /// ```
855    pub fn or(
856        self,
857        other: Optional<T, L, B::UnderlyingBound>,
858    ) -> Optional<T, L, B::UnderlyingBound> {
859        let me = self.ignore_init_none();
860        check_matching_location(&me.location, &other.location);
861
862        if L::is_top_level()
863            && !<B::UnderlyingBound as Boundedness>::BOUNDED // only if unbounded we need to use a tick
864            && let Some(tick) = me.location.try_tick()
865        {
866            let self_location = me.location().clone();
867            let out = or_inside_tick(
868                me.snapshot(&tick, nondet!(/** eventually stabilizes */)),
869                other.snapshot(&tick, nondet!(/** eventually stabilizes */)),
870            )
871            .latest();
872
873            Optional::new(self_location, out.ir_node.replace(HydroNode::Placeholder))
874        } else {
875            Optional::new(
876                me.location.clone(),
877                HydroNode::ChainFirst {
878                    first: Box::new(me.ir_node.replace(HydroNode::Placeholder)),
879                    second: Box::new(other.ir_node.replace(HydroNode::Placeholder)),
880                    metadata: me
881                        .location
882                        .new_node_metadata(Optional::<T, L, B::UnderlyingBound>::collection_kind()),
883                },
884            )
885        }
886    }
887
888    /// Gets the contents of `self` when it has a value, otherwise passes through `other`.
889    ///
890    /// Like [`Option::unwrap_or`], this is helpful for defining a fallback for an [`Optional`].
891    /// If the fallback is not always defined (an [`Optional`]), you can use [`Optional::or`].
892    ///
893    /// If the inputs are [`Unbounded`], the output will be asynchronously updated as the contents
894    /// of the inputs change (including to/from null states).
895    ///
896    /// # Example
897    /// ```rust
898    /// # #[cfg(feature = "deploy")] {
899    /// # use hydro_lang::prelude::*;
900    /// # use futures::StreamExt;
901    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
902    /// let tick = process.tick();
903    /// // ticks are lazy by default, forces the later ticks to run
904    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
905    ///
906    /// let some_first_tick = tick.optional_first_tick(q!(123));
907    /// some_first_tick
908    ///     .unwrap_or(tick.singleton(q!(456)))
909    ///     .all_ticks()
910    /// # }, |mut stream| async move {
911    /// // [123 /* first tick */, 456 /* second tick */, 456 /* third tick */, 456, ...]
912    /// # for w in vec![123, 456, 456, 456] {
913    /// #     assert_eq!(stream.next().await.unwrap(), w);
914    /// # }
915    /// # }));
916    /// # }
917    /// ```
918    pub fn unwrap_or(
919        self,
920        other: Singleton<T, L, B::UnderlyingBound>,
921    ) -> Singleton<T, L, B::UnderlyingBound> {
922        let res_option = self.or(other.into());
923        Singleton::new(
924            res_option.location.clone(),
925            HydroNode::Cast {
926                inner: Box::new(res_option.ir_node.replace(HydroNode::Placeholder)),
927                metadata: res_option.location.new_node_metadata(Singleton::<
928                    T,
929                    L,
930                    B::UnderlyingBound,
931                >::collection_kind(
932                )),
933            },
934        )
935    }
936
937    /// Gets the contents of `self` when it has a value, otherwise returns the default value of `T`.
938    ///
939    /// Like [`Option::unwrap_or_default`], this is helpful for defining a fallback for an
940    /// [`Optional`] when the default value of the type is a suitable fallback.
941    ///
942    /// # Example
943    /// ```rust
944    /// # #[cfg(feature = "deploy")] {
945    /// # use hydro_lang::prelude::*;
946    /// # use futures::StreamExt;
947    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
948    /// let tick = process.tick();
949    /// // ticks are lazy by default, forces the later ticks to run
950    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
951    ///
952    /// let some_first_tick = tick.optional_first_tick(q!(123i32));
953    /// some_first_tick.unwrap_or_default().all_ticks()
954    /// # }, |mut stream| async move {
955    /// // [123 /* first tick */, 0 /* second tick */, 0 /* third tick */, 0, ...]
956    /// # for w in vec![123, 0, 0, 0] {
957    /// #     assert_eq!(stream.next().await.unwrap(), w);
958    /// # }
959    /// # }));
960    /// # }
961    /// ```
962    pub fn unwrap_or_default(self) -> Singleton<T, L, B::UnderlyingBound>
963    where
964        T: Default + Clone,
965    {
966        self.into_singleton().map(q!(|v| v.unwrap_or_default()))
967    }
968
969    /// Converts this optional into a [`Singleton`] with a Rust [`Option`] as its contents.
970    ///
971    /// Useful for writing custom Rust code that needs to interact with both the null and non-null
972    /// states of the [`Optional`]. When possible, you should use the native APIs on [`Optional`]
973    /// so that Hydro can skip any computation on null values.
974    ///
975    /// # Example
976    /// ```rust
977    /// # #[cfg(feature = "deploy")] {
978    /// # use hydro_lang::prelude::*;
979    /// # use futures::StreamExt;
980    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
981    /// let tick = process.tick();
982    /// // ticks are lazy by default, forces the later ticks to run
983    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
984    ///
985    /// let some_first_tick = tick.optional_first_tick(q!(123));
986    /// some_first_tick.into_singleton().all_ticks()
987    /// # }, |mut stream| async move {
988    /// // [Some(123) /* first tick */, None /* second tick */, None /* third tick */, None, ...]
989    /// # for w in vec![Some(123), None, None, None] {
990    /// #     assert_eq!(stream.next().await.unwrap(), w);
991    /// # }
992    /// # }));
993    /// # }
994    /// ```
995    pub fn into_singleton(self) -> Singleton<Option<T>, L, B::UnderlyingBound>
996    where
997        T: Clone,
998    {
999        let none: syn::Expr = parse_quote!(::std::option::Option::None);
1000
1001        let none_singleton = Singleton::new(
1002            self.location.clone(),
1003            HydroNode::SingletonSource {
1004                value: none.into(),
1005                first_tick_only: false,
1006                metadata: self.location.new_node_metadata(Singleton::<
1007                    Option<T>,
1008                    L,
1009                    B::UnderlyingBound,
1010                >::collection_kind()),
1011            },
1012        );
1013
1014        self.map(q!(|v| Some(v))).unwrap_or(none_singleton)
1015    }
1016
1017    /// Returns a [`Singleton`] containing `true` if this optional has a value, `false` otherwise.
1018    ///
1019    /// # Example
1020    /// ```rust
1021    /// # #[cfg(feature = "deploy")] {
1022    /// # use hydro_lang::prelude::*;
1023    /// # use futures::StreamExt;
1024    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1025    /// let tick = process.tick();
1026    /// // ticks are lazy by default, forces the second tick to run
1027    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1028    ///
1029    /// let some_first_tick = tick.optional_first_tick(q!(42));
1030    /// some_first_tick.is_some().all_ticks()
1031    /// # }, |mut stream| async move {
1032    /// // [true /* first tick */, false /* second tick */, ...]
1033    /// # for w in vec![true, false] {
1034    /// #     assert_eq!(stream.next().await.unwrap(), w);
1035    /// # }
1036    /// # }));
1037    /// # }
1038    /// ```
1039    #[expect(clippy::wrong_self_convention, reason = "Stream naming")]
1040    pub fn is_some(self) -> Singleton<bool, L, B::UnderlyingBound> {
1041        self.map(q!(|_| ()))
1042            .into_singleton()
1043            .map(q!(|o| o.is_some()))
1044    }
1045
1046    /// Returns a [`Singleton`] containing `true` if this optional is null, `false` otherwise.
1047    ///
1048    /// # Example
1049    /// ```rust
1050    /// # #[cfg(feature = "deploy")] {
1051    /// # use hydro_lang::prelude::*;
1052    /// # use futures::StreamExt;
1053    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1054    /// let tick = process.tick();
1055    /// // ticks are lazy by default, forces the second tick to run
1056    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1057    ///
1058    /// let some_first_tick = tick.optional_first_tick(q!(42));
1059    /// some_first_tick.is_none().all_ticks()
1060    /// # }, |mut stream| async move {
1061    /// // [false /* first tick */, true /* second tick */, ...]
1062    /// # for w in vec![false, true] {
1063    /// #     assert_eq!(stream.next().await.unwrap(), w);
1064    /// # }
1065    /// # }));
1066    /// # }
1067    /// ```
1068    #[expect(clippy::wrong_self_convention, reason = "Stream naming")]
1069    pub fn is_none(self) -> Singleton<bool, L, B::UnderlyingBound> {
1070        self.map(q!(|_| ()))
1071            .into_singleton()
1072            .map(q!(|o| o.is_none()))
1073    }
1074
1075    /// Returns a [`Singleton`] containing `true` if both optionals are non-null and their
1076    /// values are equal, `false` otherwise (including when either is null).
1077    ///
1078    /// # Example
1079    /// ```rust
1080    /// # #[cfg(feature = "deploy")] {
1081    /// # use hydro_lang::prelude::*;
1082    /// # use futures::StreamExt;
1083    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1084    /// let tick = process.tick();
1085    /// // ticks are lazy by default, forces the second tick to run
1086    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1087    ///
1088    /// let a = tick.optional_first_tick(q!(5)); // Some(5), None
1089    /// let b = tick.optional_first_tick(q!(5)); // Some(5), None
1090    /// a.is_some_and_equals(b).all_ticks()
1091    /// # }, |mut stream| async move {
1092    /// // [true, false]
1093    /// # for w in vec![true, false] {
1094    /// #     assert_eq!(stream.next().await.unwrap(), w);
1095    /// # }
1096    /// # }));
1097    /// # }
1098    /// ```
1099    #[expect(clippy::wrong_self_convention, reason = "Stream naming")]
1100    pub fn is_some_and_equals(self, other: Optional<T, L, B>) -> Singleton<bool, L, B>
1101    where
1102        T: PartialEq + Clone,
1103        B: IsBounded,
1104    {
1105        self.into_singleton()
1106            .zip(other.into_singleton())
1107            .map(q!(|(a, b)| a.is_some() && a == b))
1108    }
1109
1110    /// An operator which allows you to "name" a `HydroNode`.
1111    /// This is only used for testing, to correlate certain `HydroNode`s with IDs.
1112    pub fn ir_node_named(self, name: &str) -> Optional<T, L, B> {
1113        {
1114            let mut node = self.ir_node.borrow_mut();
1115            let metadata = node.metadata_mut();
1116            metadata.tag = Some(name.to_owned());
1117        }
1118        self
1119    }
1120
1121    /// Drops the [`InitNone`] guarantee of the [`Optional`], erasing it to its
1122    /// [`OptionalBound::UnderlyingBound`] (i.e. [`Unbounded`] for [`InitNone`]).
1123    pub fn ignore_init_none(self) -> Optional<T, L, B::UnderlyingBound> {
1124        if <B as OptionalBound>::bound_kind() == <B::UnderlyingBound as OptionalBound>::bound_kind()
1125        {
1126            Optional::new(
1127                self.location.clone(),
1128                self.ir_node.replace(HydroNode::Placeholder),
1129            )
1130        } else {
1131            Optional::new(
1132                self.location.clone(),
1133                HydroNode::Cast {
1134                    inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1135                    metadata: self
1136                        .location
1137                        .new_node_metadata(Optional::<T, L, B::UnderlyingBound>::collection_kind()),
1138                },
1139            )
1140        }
1141    }
1142
1143    /// Strengthens the boundedness guarantee to `Bounded`, given that `B: IsBounded`, which
1144    /// implies that `B == Bounded`.
1145    pub fn make_bounded(self) -> Optional<T, L, Bounded>
1146    where
1147        B: IsBounded,
1148    {
1149        Optional::new(
1150            self.location.clone(),
1151            self.ir_node.replace(HydroNode::Placeholder),
1152        )
1153    }
1154
1155    /// Clones this bounded optional into a tick, returning a optional that has the
1156    /// same value as the outer optional. Because the outer optional is bounded, this
1157    /// is deterministic because there is only a single immutable version.
1158    pub fn clone_into_tick(self, tick: &Tick<L>) -> Optional<T, Tick<L>, Bounded>
1159    where
1160        B: IsBounded,
1161        T: Clone,
1162    {
1163        // TODO(shadaj): avoid printing simulator logs for this snapshot
1164        let inner = self.snapshot(
1165            tick,
1166            nondet!(/** bounded top-level optional so deterministic */),
1167        );
1168        Optional::new(tick.clone(), inner.ir_node.replace(HydroNode::Placeholder))
1169    }
1170
1171    /// Converts this optional into a [`Stream`] containing a single element, the value, if it is
1172    /// non-null. Otherwise, the stream is empty.
1173    ///
1174    /// # Example
1175    /// ```rust
1176    /// # #[cfg(feature = "deploy")] {
1177    /// # use hydro_lang::prelude::*;
1178    /// # use futures::StreamExt;
1179    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1180    /// # let tick = process.tick();
1181    /// # // ticks are lazy by default, forces the second tick to run
1182    /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1183    /// # let batch_first_tick = process
1184    /// #   .source_iter(q!(vec![]))
1185    /// #   .batch(&tick, nondet!(/** test */));
1186    /// # let batch_second_tick = process
1187    /// #   .source_iter(q!(vec![123, 456]))
1188    /// #   .batch(&tick, nondet!(/** test */))
1189    /// #   .defer_tick(); // appears on the second tick
1190    /// # let input_batch = batch_first_tick.chain(batch_second_tick);
1191    /// input_batch // first tick: [], second tick: [123, 456]
1192    ///     .clone()
1193    ///     .max()
1194    ///     .into_stream()
1195    ///     .chain(input_batch)
1196    ///     .all_ticks()
1197    /// # }, |mut stream| async move {
1198    /// // [456, 123, 456]
1199    /// # for w in vec![456, 123, 456] {
1200    /// #     assert_eq!(stream.next().await.unwrap(), w);
1201    /// # }
1202    /// # }));
1203    /// # }
1204    /// ```
1205    pub fn into_stream(self) -> Stream<T, L, Bounded, TotalOrder, ExactlyOnce>
1206    where
1207        B: IsBounded,
1208    {
1209        Stream::new(
1210            self.location.clone(),
1211            HydroNode::Cast {
1212                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1213                metadata: self.location.new_node_metadata(Stream::<
1214                    T,
1215                    Tick<L>,
1216                    Bounded,
1217                    TotalOrder,
1218                    ExactlyOnce,
1219                >::collection_kind()),
1220            },
1221        )
1222    }
1223
1224    /// Filters this optional, passing through the value if the boolean signal is `true`,
1225    /// otherwise the output is null.
1226    ///
1227    /// # Example
1228    /// ```rust
1229    /// # #[cfg(feature = "deploy")] {
1230    /// # use hydro_lang::prelude::*;
1231    /// # use futures::StreamExt;
1232    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1233    /// let tick = process.tick();
1234    /// // ticks are lazy by default, forces the second tick to run
1235    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1236    ///
1237    /// let some_first_tick = tick.optional_first_tick(q!(()));
1238    /// let signal = some_first_tick.is_some(); // true on first tick, false on second
1239    /// let batch_first_tick = process
1240    ///   .source_iter(q!(vec![456]))
1241    ///   .batch(&tick, nondet!(/** test */));
1242    /// let batch_second_tick = process
1243    ///   .source_iter(q!(vec![789]))
1244    ///   .batch(&tick, nondet!(/** test */))
1245    ///   .defer_tick();
1246    /// batch_first_tick.chain(batch_second_tick).first()
1247    ///   .filter_if(signal)
1248    ///   .unwrap_or(tick.singleton(q!(0)))
1249    ///   .all_ticks()
1250    /// # }, |mut stream| async move {
1251    /// // [456, 0]
1252    /// # for w in vec![456, 0] {
1253    /// #     assert_eq!(stream.next().await.unwrap(), w);
1254    /// # }
1255    /// # }));
1256    /// # }
1257    /// ```
1258    pub fn filter_if(self, signal: Singleton<bool, L, B>) -> Optional<T, L, B>
1259    where
1260        B: IsBounded,
1261    {
1262        self.zip(signal.filter(q!(|b| *b))).map(q!(|(d, _)| d))
1263    }
1264
1265    /// Filters this optional, passing through the optional value if it is non-null **and** the
1266    /// argument (a [`Bounded`] [`Optional`]`) is non-null, otherwise the output is null.
1267    ///
1268    /// Useful for conditionally processing, such as only emitting an optional's value outside
1269    /// a tick if some other condition is satisfied.
1270    ///
1271    /// # Example
1272    /// ```rust
1273    /// # #[cfg(feature = "deploy")] {
1274    /// # use hydro_lang::prelude::*;
1275    /// # use futures::StreamExt;
1276    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1277    /// let tick = process.tick();
1278    /// // ticks are lazy by default, forces the second tick to run
1279    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1280    ///
1281    /// let batch_first_tick = process
1282    ///   .source_iter(q!(vec![]))
1283    ///   .batch(&tick, nondet!(/** test */));
1284    /// let batch_second_tick = process
1285    ///   .source_iter(q!(vec![456]))
1286    ///   .batch(&tick, nondet!(/** test */))
1287    ///   .defer_tick(); // appears on the second tick
1288    /// let some_on_first_tick = tick.optional_first_tick(q!(()));
1289    /// batch_first_tick.chain(batch_second_tick).first()
1290    ///   .filter_if_some(some_on_first_tick)
1291    ///   .unwrap_or(tick.singleton(q!(789)))
1292    ///   .all_ticks()
1293    /// # }, |mut stream| async move {
1294    /// // [789, 789]
1295    /// # for w in vec![789, 789] {
1296    /// #     assert_eq!(stream.next().await.unwrap(), w);
1297    /// # }
1298    /// # }));
1299    /// # }
1300    /// ```
1301    #[deprecated(note = "use `filter_if` with `Optional::is_some()` instead")]
1302    pub fn filter_if_some<U>(self, signal: Optional<U, L, B>) -> Optional<T, L, B>
1303    where
1304        B: IsBounded,
1305    {
1306        self.filter_if(signal.is_some())
1307    }
1308
1309    /// Filters this optional, passing through the optional value if it is non-null **and** the
1310    /// argument (a [`Bounded`] [`Optional`]`) is _null_, otherwise the output is null.
1311    ///
1312    /// Useful for conditionally processing, such as only emitting an optional's value outside
1313    /// a tick if some other condition is satisfied.
1314    ///
1315    /// # Example
1316    /// ```rust
1317    /// # #[cfg(feature = "deploy")] {
1318    /// # use hydro_lang::prelude::*;
1319    /// # use futures::StreamExt;
1320    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1321    /// let tick = process.tick();
1322    /// // ticks are lazy by default, forces the second tick to run
1323    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1324    ///
1325    /// let batch_first_tick = process
1326    ///   .source_iter(q!(vec![]))
1327    ///   .batch(&tick, nondet!(/** test */));
1328    /// let batch_second_tick = process
1329    ///   .source_iter(q!(vec![456]))
1330    ///   .batch(&tick, nondet!(/** test */))
1331    ///   .defer_tick(); // appears on the second tick
1332    /// let some_on_first_tick = tick.optional_first_tick(q!(()));
1333    /// batch_first_tick.chain(batch_second_tick).first()
1334    ///   .filter_if_none(some_on_first_tick)
1335    ///   .unwrap_or(tick.singleton(q!(789)))
1336    ///   .all_ticks()
1337    /// # }, |mut stream| async move {
1338    /// // [789, 789]
1339    /// # for w in vec![789, 456] {
1340    /// #     assert_eq!(stream.next().await.unwrap(), w);
1341    /// # }
1342    /// # }));
1343    /// # }
1344    /// ```
1345    #[deprecated(note = "use `filter_if` with `!Optional::is_some()` instead")]
1346    pub fn filter_if_none<U>(self, other: Optional<U, L, B>) -> Optional<T, L, B>
1347    where
1348        B: IsBounded,
1349    {
1350        self.filter_if(other.is_none())
1351    }
1352
1353    /// If `self` is null, emits a null optional, but if it non-null, emits `value`.
1354    ///
1355    /// Useful for gating the release of a [`Singleton`] on a condition of the [`Optional`]
1356    /// having a value, such as only releasing a piece of state if the node is the leader.
1357    ///
1358    /// # Example
1359    /// ```rust
1360    /// # #[cfg(feature = "deploy")] {
1361    /// # use hydro_lang::prelude::*;
1362    /// # use futures::StreamExt;
1363    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1364    /// let tick = process.tick();
1365    /// // ticks are lazy by default, forces the second tick to run
1366    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1367    ///
1368    /// let some_on_first_tick = tick.optional_first_tick(q!(()));
1369    /// some_on_first_tick
1370    ///     .if_some_then(tick.singleton(q!(456)))
1371    ///     .unwrap_or(tick.singleton(q!(123)))
1372    /// # .all_ticks()
1373    /// # }, |mut stream| async move {
1374    /// // 456 (first tick) ~> 123 (second tick onwards)
1375    /// # for w in vec![456, 123, 123] {
1376    /// #     assert_eq!(stream.next().await.unwrap(), w);
1377    /// # }
1378    /// # }));
1379    /// # }
1380    /// ```
1381    #[deprecated(note = "use `filter_if` with `Optional::is_some()` instead")]
1382    pub fn if_some_then<U>(self, value: Singleton<U, L, B>) -> Optional<U, L, B>
1383    where
1384        B: IsBounded,
1385    {
1386        value.filter_if(self.is_some())
1387    }
1388}
1389
1390impl<'a, K, V, L, B: OptionalBound> Optional<(K, V), L, B>
1391where
1392    L: Location<'a>,
1393{
1394    /// Converts this optional into a [`KeyedSingleton`] containing a single entry with the
1395    /// key-value pair of this [`Optional`].
1396    ///
1397    /// If this [`Optional`] is [`Bounded`], the [`KeyedSingleton`] will be [`Bounded`] as well
1398    /// if it is [`Unbounded`], the [`KeyedSingleton`] will be [`Unbounded`], which means that
1399    /// the entry will be updated and appear / disappear according to the state of the
1400    /// [`Optional`].
1401    pub fn into_keyed_singleton(self) -> KeyedSingleton<K, V, L, B::UnderlyingBound> {
1402        KeyedSingleton::new(
1403            self.location.clone(),
1404            HydroNode::Cast {
1405                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1406                metadata: self.location.new_node_metadata(KeyedSingleton::<
1407                    K,
1408                    V,
1409                    L,
1410                    B::UnderlyingBound,
1411                >::collection_kind()),
1412            },
1413        )
1414    }
1415}
1416
1417impl<'a, T, L, B: OptionalBound> Optional<T, Atomic<L>, B>
1418where
1419    L: Location<'a>,
1420{
1421    /// Returns an optional value corresponding to the latest snapshot of the optional
1422    /// being atomically processed. The snapshot at tick `t + 1` is guaranteed to include
1423    /// at least all relevant data that contributed to the snapshot at tick `t`. Furthermore,
1424    /// all snapshots of this optional into the atomic-associated tick will observe the
1425    /// same value each tick.
1426    ///
1427    /// # Non-Determinism
1428    /// Because this picks a snapshot of a optional whose value is continuously changing,
1429    /// the output optional has a non-deterministic value since the snapshot can be at an
1430    /// arbitrary point in time.
1431    pub fn snapshot_atomic<L2: Location<'a, DropConsistency = L::DropConsistency>>(
1432        self,
1433        tick: &Tick<L2>,
1434        _nondet: NonDet,
1435    ) -> Optional<T, Tick<L::DropConsistency>, Bounded> {
1436        assert_eq!(
1437            Location::id(tick.parent_location()),
1438            Location::id(self.location.tick.parent_location())
1439        );
1440        Optional::new(
1441            tick.drop_consistency(),
1442            HydroNode::Batch {
1443                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1444                metadata: tick
1445                    .new_node_metadata(Optional::<T, Tick<L>, Bounded>::collection_kind()),
1446            },
1447        )
1448    }
1449}
1450
1451impl<'a, T, L, B: OptionalBound> Optional<T, L, B>
1452where
1453    L: Location<'a>,
1454{
1455    /// Given a tick, returns a optional value corresponding to a snapshot of the optional
1456    /// as of that tick. The snapshot at tick `t + 1` is guaranteed to include at least all
1457    /// relevant data that contributed to the snapshot at tick `t`.
1458    ///
1459    /// # Non-Determinism
1460    /// Because this picks a snapshot of a optional whose value is continuously changing,
1461    /// the output optional has a non-deterministic value since the snapshot can be at an
1462    /// arbitrary point in time.
1463    pub fn snapshot<L2: Location<'a, DropConsistency = L::DropConsistency>>(
1464        self,
1465        tick: &Tick<L2>,
1466        _nondet: NonDet,
1467    ) -> Optional<T, Tick<L::DropConsistency>, Bounded> {
1468        assert_eq!(
1469            Location::id(tick.parent_location()),
1470            Location::id(&self.location)
1471        );
1472        Optional::new(
1473            tick.drop_consistency(),
1474            HydroNode::Batch {
1475                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1476                metadata: tick
1477                    .new_node_metadata(Optional::<T, Tick<L>, Bounded>::collection_kind()),
1478            },
1479        )
1480    }
1481
1482    /// Eagerly samples the optional as fast as possible, returning a stream of snapshots
1483    /// with order corresponding to increasing prefixes of data contributing to the optional.
1484    ///
1485    /// This requires the optional to be null-only-initially ([`IsInitNone`]): a general
1486    /// [`Unbounded`] optional can *become null again*, which a `Stream<T>` of samples cannot
1487    /// represent (dropping the null transitions would leave stale values observable). To
1488    /// sample an [`Unbounded`] optional, use [`Optional::into_singleton`] first to observe
1489    /// `Option<T>` snapshots (including `None`).
1490    ///
1491    /// # Non-Determinism
1492    /// At runtime, the optional will be arbitrarily sampled as fast as possible, but due
1493    /// to non-deterministic batching and arrival of inputs, the output stream is
1494    /// non-deterministic.
1495    pub fn sample_eager(
1496        self,
1497        nondet: NonDet,
1498    ) -> Stream<T, L::DropConsistency, Unbounded, TotalOrder, AtLeastOnce>
1499    where
1500        B: IsInitNone,
1501    {
1502        let tick = self.location.tick();
1503        self.snapshot(&tick, nondet).all_ticks().weaken_retries()
1504    }
1505
1506    /// Given a time interval, returns a stream corresponding to snapshots of the optional
1507    /// value taken at various points in time. Because the input optional may be
1508    /// [`Unbounded`], there are no guarantees on what these snapshots are other than they
1509    /// represent the value of the optional given some prefix of the streams leading up to
1510    /// it.
1511    ///
1512    /// # Non-Determinism
1513    /// The output stream is non-deterministic in which elements are sampled, since this
1514    /// is controlled by a clock.
1515    #[cfg(feature = "tokio")]
1516    pub fn sample_every(
1517        self,
1518        interval: impl QuotedWithContext<'a, std::time::Duration, L> + Copy + 'a,
1519        nondet: NonDet,
1520    ) -> Stream<T, L::DropConsistency, Unbounded, TotalOrder, AtLeastOnce>
1521    where
1522        L: TopLevel<'a>,
1523    {
1524        let samples = self.location.source_interval(interval);
1525        let tick = self.location.tick();
1526
1527        self.snapshot(&tick, nondet)
1528            .filter_if(
1529                samples
1530                    .batch(
1531                        &tick,
1532                        nondet!(
1533                            /// sample timing is captured by the caller's guard
1534                            nondet
1535                        ),
1536                    )
1537                    .first()
1538                    .is_some(),
1539            )
1540            .all_ticks()
1541            .weaken_retries()
1542    }
1543}
1544
1545impl<'a, T, L> Optional<T, Tick<L>, Bounded>
1546where
1547    L: Location<'a>,
1548{
1549    /// Asynchronously yields the value of this singleton outside the tick as an unbounded stream,
1550    /// which will stream the value computed in _each_ tick as a separate stream element (skipping
1551    /// null values).
1552    ///
1553    /// Unlike [`Optional::latest`], the value computed in each tick is emitted separately,
1554    /// producing one element in the output for each (non-null) tick. This is useful for batched
1555    /// computations, where the results from each tick must be combined together.
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    /// # let tick = process.tick();
1564    /// # // ticks are lazy by default, forces the second tick to run
1565    /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1566    /// # let batch_first_tick = process
1567    /// #   .source_iter(q!(vec![]))
1568    /// #   .batch(&tick, nondet!(/** test */));
1569    /// # let batch_second_tick = process
1570    /// #   .source_iter(q!(vec![1, 2, 3]))
1571    /// #   .batch(&tick, nondet!(/** test */))
1572    /// #   .defer_tick(); // appears on the second tick
1573    /// # let input_batch = batch_first_tick.chain(batch_second_tick);
1574    /// input_batch // first tick: [], second tick: [1, 2, 3]
1575    ///     .max()
1576    ///     .all_ticks()
1577    /// # }, |mut stream| async move {
1578    /// // [3]
1579    /// # for w in vec![3] {
1580    /// #     assert_eq!(stream.next().await.unwrap(), w);
1581    /// # }
1582    /// # }));
1583    /// # }
1584    /// ```
1585    pub fn all_ticks(self) -> Stream<T, L, Unbounded, TotalOrder, ExactlyOnce> {
1586        self.into_stream().all_ticks()
1587    }
1588
1589    /// Synchronously yields the value of this optional outside the tick as an unbounded stream,
1590    /// which will stream the value computed in _each_ tick as a separate stream element.
1591    ///
1592    /// Unlike [`Optional::all_ticks`], this preserves synchronous execution, as the output stream
1593    /// is emitted in an [`Atomic`] context that will process elements synchronously with the input
1594    /// optional's [`Tick`] context.
1595    pub fn all_ticks_atomic(self) -> Stream<T, Atomic<L>, Unbounded, TotalOrder, ExactlyOnce> {
1596        self.into_stream().all_ticks_atomic()
1597    }
1598
1599    /// Asynchronously yields this optional outside the tick as an unbounded optional, which will
1600    /// be asynchronously updated with the latest value of the optional inside the tick, including
1601    /// whether the optional is null or not.
1602    ///
1603    /// This converts a bounded value _inside_ a tick into an asynchronous value outside the
1604    /// tick that tracks the inner value. This is useful for getting the value as of the
1605    /// "most recent" tick, but note that updates are propagated asynchronously outside the tick.
1606    ///
1607    /// # Example
1608    /// ```rust
1609    /// # #[cfg(feature = "deploy")] {
1610    /// # use hydro_lang::prelude::*;
1611    /// # use futures::StreamExt;
1612    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1613    /// # let tick = process.tick();
1614    /// # // ticks are lazy by default, forces the second tick to run
1615    /// # tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1616    /// # let batch_first_tick = process
1617    /// #   .source_iter(q!(vec![]))
1618    /// #   .batch(&tick, nondet!(/** test */));
1619    /// # let batch_second_tick = process
1620    /// #   .source_iter(q!(vec![1, 2, 3]))
1621    /// #   .batch(&tick, nondet!(/** test */))
1622    /// #   .defer_tick(); // appears on the second tick
1623    /// # let input_batch = batch_first_tick.chain(batch_second_tick);
1624    /// input_batch // first tick: [], second tick: [1, 2, 3]
1625    ///     .max()
1626    ///     .latest()
1627    /// # .into_singleton()
1628    /// # .sample_eager(nondet!(/** test */))
1629    /// # }, |mut stream| async move {
1630    /// // asynchronously changes from None ~> 3
1631    /// # for w in vec![None, Some(3)] {
1632    /// #     assert_eq!(stream.next().await.unwrap(), w);
1633    /// # }
1634    /// # }));
1635    /// # }
1636    /// ```
1637    pub fn latest(self) -> Optional<T, L, Unbounded> {
1638        Optional::new(
1639            self.location.parent_location().clone(),
1640            HydroNode::YieldConcat {
1641                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1642                metadata: self
1643                    .location
1644                    .parent_location()
1645                    .new_node_metadata(Optional::<T, L, Unbounded>::collection_kind()),
1646            },
1647        )
1648    }
1649
1650    /// Synchronously yields this optional outside the tick as an unbounded optional, which will
1651    /// be updated with the latest value of the optional inside the tick.
1652    ///
1653    /// Unlike [`Optional::latest`], this preserves synchronous execution, as the output optional
1654    /// is emitted in an [`Atomic`] context that will process elements synchronously with the input
1655    /// optional's [`Tick`] context.
1656    pub fn latest_atomic(self) -> Optional<T, Atomic<L>, Unbounded> {
1657        let out_location = Atomic {
1658            tick: self.location.clone(),
1659        };
1660
1661        Optional::new(
1662            out_location.clone(),
1663            HydroNode::YieldConcat {
1664                inner: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1665                metadata: out_location
1666                    .new_node_metadata(Optional::<T, Atomic<L>, Unbounded>::collection_kind()),
1667            },
1668        )
1669    }
1670
1671    /// Shifts the state in `self` to the **next tick**, so that the returned optional at tick `T`
1672    /// always has the state of `self` at tick `T - 1`.
1673    ///
1674    /// At tick `0`, the output optional is null, since there is no previous tick.
1675    ///
1676    /// This operator enables stateful iterative processing with ticks, by sending data from one
1677    /// tick to the next. For example, you can use it to compare state across consecutive batches.
1678    ///
1679    /// # Example
1680    /// ```rust
1681    /// # #[cfg(feature = "deploy")] {
1682    /// # use hydro_lang::prelude::*;
1683    /// # use futures::StreamExt;
1684    /// # tokio_test::block_on(hydro_lang::test_util::stream_transform_test(|process| {
1685    /// let tick = process.tick();
1686    /// // ticks are lazy by default, forces the second tick to run
1687    /// tick.spin_batch(q!(1)).all_ticks().for_each(q!(|_| {}));
1688    ///
1689    /// let batch_first_tick = process
1690    ///   .source_iter(q!(vec![1, 2]))
1691    ///   .batch(&tick, nondet!(/** test */));
1692    /// let batch_second_tick = process
1693    ///   .source_iter(q!(vec![3, 4]))
1694    ///   .batch(&tick, nondet!(/** test */))
1695    ///   .defer_tick(); // appears on the second tick
1696    /// let current_tick_sum = batch_first_tick.chain(batch_second_tick)
1697    ///   .reduce(q!(|state, v| *state += v));
1698    ///
1699    /// current_tick_sum.clone().into_singleton().zip(
1700    ///   current_tick_sum.defer_tick().into_singleton() // state from previous tick
1701    /// ).all_ticks()
1702    /// # }, |mut stream| async move {
1703    /// // [(Some(3), None) /* first tick */, (Some(7), Some(3)) /* second tick */]
1704    /// # for w in vec![(Some(3), None), (Some(7), Some(3))] {
1705    /// #     assert_eq!(stream.next().await.unwrap(), w);
1706    /// # }
1707    /// # }));
1708    /// # }
1709    /// ```
1710    pub fn defer_tick(self) -> Optional<T, Tick<L>, Bounded> {
1711        Optional::new(
1712            self.location.clone(),
1713            HydroNode::DeferTick {
1714                input: Box::new(self.ir_node.replace(HydroNode::Placeholder)),
1715                metadata: self.location.new_node_metadata(Self::collection_kind()),
1716            },
1717        )
1718    }
1719}
1720
1721#[cfg(test)]
1722mod tests {
1723    #[cfg(feature = "deploy")]
1724    use futures::StreamExt;
1725    #[cfg(feature = "deploy")]
1726    use hydro_deploy::Deployment;
1727    #[cfg(any(feature = "deploy", feature = "sim"))]
1728    use stageleft::q;
1729
1730    #[cfg(feature = "deploy")]
1731    use super::Optional;
1732    #[cfg(any(feature = "deploy", feature = "sim"))]
1733    use crate::compile::builder::FlowBuilder;
1734    #[cfg(any(feature = "deploy", feature = "sim"))]
1735    use crate::location::Location;
1736    #[cfg(feature = "deploy")]
1737    use crate::nondet::nondet;
1738
1739    #[cfg(feature = "deploy")]
1740    #[tokio::test]
1741    async fn optional_or_cardinality() {
1742        let mut deployment = Deployment::new();
1743
1744        let mut flow = FlowBuilder::new();
1745        let node = flow.process::<()>();
1746        let external = flow.external::<()>();
1747
1748        let node_tick = node.tick();
1749        let tick_singleton = node_tick.singleton(q!(123));
1750        let tick_optional_inhabited: Optional<_, _, _> = tick_singleton.into();
1751        let counts = tick_optional_inhabited
1752            .clone()
1753            .or(tick_optional_inhabited)
1754            .into_stream()
1755            .count()
1756            .all_ticks()
1757            .send_bincode_external(&external);
1758
1759        let nodes = flow
1760            .with_process(&node, deployment.Localhost())
1761            .with_external(&external, deployment.Localhost())
1762            .deploy(&mut deployment);
1763
1764        deployment.deploy().await.unwrap();
1765
1766        let mut external_out = nodes.connect(counts).await;
1767
1768        deployment.start().await.unwrap();
1769
1770        assert_eq!(external_out.next().await.unwrap(), 1);
1771    }
1772
1773    #[cfg(feature = "deploy")]
1774    #[tokio::test]
1775    async fn into_singleton_top_level_none_cardinality() {
1776        let mut deployment = Deployment::new();
1777
1778        let mut flow = FlowBuilder::new();
1779        let node = flow.process::<()>();
1780        let external = flow.external::<()>();
1781
1782        let node_tick = node.tick();
1783        let top_level_none = node.singleton(q!(123)).filter(q!(|_| false));
1784        let into_singleton = top_level_none.into_singleton();
1785
1786        let tick_driver = node.spin();
1787
1788        let counts = into_singleton
1789            .snapshot(&node_tick, nondet!(/** test */))
1790            .into_stream()
1791            .count()
1792            .zip(tick_driver.batch(&node_tick, nondet!(/** test */)).count())
1793            .map(q!(|(c, _)| c))
1794            .all_ticks()
1795            .send_bincode_external(&external);
1796
1797        let nodes = flow
1798            .with_process(&node, deployment.Localhost())
1799            .with_external(&external, deployment.Localhost())
1800            .deploy(&mut deployment);
1801
1802        deployment.deploy().await.unwrap();
1803
1804        let mut external_out = nodes.connect(counts).await;
1805
1806        deployment.start().await.unwrap();
1807
1808        assert_eq!(external_out.next().await.unwrap(), 1);
1809        assert_eq!(external_out.next().await.unwrap(), 1);
1810        assert_eq!(external_out.next().await.unwrap(), 1);
1811    }
1812
1813    #[cfg(feature = "deploy")]
1814    #[tokio::test]
1815    async fn into_singleton_unbounded_top_level_none_cardinality() {
1816        let mut deployment = Deployment::new();
1817
1818        let mut flow = FlowBuilder::new();
1819        let node = flow.process::<()>();
1820        let external = flow.external::<()>();
1821
1822        let top_level_none = node
1823            .tick()
1824            .singleton(q!(123))
1825            .latest()
1826            .filter(q!(|_| false));
1827        let into_singleton = top_level_none.into_singleton();
1828
1829        let tick_driver = node.spin();
1830
1831        let tick_later = node.tick();
1832        let counts = into_singleton
1833            .snapshot(&tick_later, nondet!(/** test */))
1834            .into_stream()
1835            .count()
1836            .zip(tick_driver.batch(&tick_later, nondet!(/** test */)).count())
1837            .map(q!(|(c, _)| c))
1838            .all_ticks()
1839            .send_bincode_external(&external);
1840
1841        let nodes = flow
1842            .with_process(&node, deployment.Localhost())
1843            .with_external(&external, deployment.Localhost())
1844            .deploy(&mut deployment);
1845
1846        deployment.deploy().await.unwrap();
1847
1848        let mut external_out = nodes.connect(counts).await;
1849
1850        deployment.start().await.unwrap();
1851
1852        assert_eq!(external_out.next().await.unwrap(), 1);
1853        assert_eq!(external_out.next().await.unwrap(), 1);
1854        assert_eq!(external_out.next().await.unwrap(), 1);
1855    }
1856
1857    #[cfg(feature = "sim")]
1858    #[test]
1859    fn top_level_optional_some_into_stream_no_replay() {
1860        let mut flow = FlowBuilder::new();
1861        let node = flow.process::<()>();
1862
1863        let source_iter = node.source_iter(q!(vec![1, 2, 3, 4]));
1864        let folded = source_iter.fold(q!(|| 0), q!(|a, b| *a += b));
1865        let filtered_some = folded.filter(q!(|_| true));
1866
1867        let out_recv = filtered_some.into_stream().sim_output();
1868
1869        flow.sim().exhaustive(async || {
1870            out_recv.assert_yields_only([10]).await;
1871        });
1872    }
1873
1874    #[cfg(feature = "sim")]
1875    #[test]
1876    fn top_level_optional_none_into_stream_no_replay() {
1877        let mut flow = FlowBuilder::new();
1878        let node = flow.process::<()>();
1879
1880        let source_iter = node.source_iter(q!(vec![1, 2, 3, 4]));
1881        let folded = source_iter.fold(q!(|| 0), q!(|a, b| *a += b));
1882        let filtered_none = folded.filter(q!(|_| false));
1883
1884        let out_recv = filtered_none.into_stream().sim_output();
1885
1886        flow.sim().exhaustive(async || {
1887            out_recv.assert_yields_only([] as [i32; 0]).await;
1888        });
1889    }
1890
1891    #[cfg(feature = "deploy")]
1892    #[tokio::test]
1893    async fn test_optional_ref() {
1894        let mut deployment = Deployment::new();
1895
1896        let mut flow = FlowBuilder::new();
1897        let external = flow.external::<()>();
1898        let p1 = flow.process::<()>();
1899
1900        // Create an optional: reduce 0..5 => Some(10) (sum via reduce)
1901        let my_opt = p1.source_iter(q!(0..5i32)).reduce(q!(|a, b| *a += b));
1902
1903        let opt_ref = my_opt.by_ref();
1904
1905        // Use the optional ref in a map: unwrap_or(0) + x
1906        let out_port = p1
1907            .source_iter(q!(1..=3i32))
1908            .map(q!(|x| x + opt_ref.unwrap_or(0)))
1909            .send_bincode_external(&external);
1910
1911        let nodes = flow
1912            .with_default_optimize()
1913            .with_process(&p1, deployment.Localhost())
1914            .with_external(&external, deployment.Localhost())
1915            .deploy(&mut deployment);
1916
1917        deployment.deploy().await.unwrap();
1918
1919        let mut out_recv = nodes.connect(out_port).await;
1920
1921        deployment.start().await.unwrap();
1922
1923        let mut results = Vec::new();
1924        for _ in 0..3 {
1925            results.push(out_recv.next().await.unwrap());
1926        }
1927        results.sort();
1928        // reduce(0..5) = 10, so results should be 11, 12, 13
1929        assert_eq!(results, vec![11, 12, 13]);
1930    }
1931
1932    #[cfg(feature = "deploy")]
1933    #[tokio::test]
1934    async fn test_optional_ref_none() {
1935        let mut deployment = Deployment::new();
1936
1937        let mut flow = FlowBuilder::new();
1938        let external = flow.external::<()>();
1939        let p1 = flow.process::<()>();
1940
1941        // Create an optional from an empty source => None
1942        let my_opt = p1
1943            .source_iter(q!(std::iter::empty::<i32>()))
1944            .reduce(q!(|a, b| *a += b));
1945
1946        let opt_ref = my_opt.by_ref();
1947
1948        // Use the optional ref: should be None, so unwrap_or(99)
1949        let out_port = p1
1950            .source_iter(q!(1..=2i32))
1951            .map(q!(|x| x + opt_ref.unwrap_or(99)))
1952            .send_bincode_external(&external);
1953
1954        let nodes = flow
1955            .with_default_optimize()
1956            .with_process(&p1, deployment.Localhost())
1957            .with_external(&external, deployment.Localhost())
1958            .deploy(&mut deployment);
1959
1960        deployment.deploy().await.unwrap();
1961
1962        let mut out_recv = nodes.connect(out_port).await;
1963
1964        deployment.start().await.unwrap();
1965
1966        let mut results = Vec::new();
1967        for _ in 0..2 {
1968            results.push(out_recv.next().await.unwrap());
1969        }
1970        results.sort();
1971        // optional is None, so unwrap_or(99) => 100, 101
1972        assert_eq!(results, vec![100, 101]);
1973    }
1974
1975    #[cfg(feature = "deploy")]
1976    #[tokio::test]
1977    async fn test_optional_ref_and_consume() {
1978        let mut deployment = Deployment::new();
1979
1980        let mut flow = FlowBuilder::new();
1981        let external = flow.external::<()>();
1982        let p1 = flow.process::<()>();
1983
1984        // Use reduce to produce an Optional
1985        let my_opt = p1.source_iter(q!(0..5i32)).reduce(q!(|a, b| *a += b));
1986
1987        let opt_ref = my_opt.by_ref();
1988
1989        // Reference path
1990        let out_port_ref = p1
1991            .source_iter(q!(1..=2i32))
1992            .map(q!(|x| x + opt_ref.unwrap_or(0)))
1993            .send_bincode_external(&external);
1994
1995        let nodes = flow
1996            .with_default_optimize()
1997            .with_process(&p1, deployment.Localhost())
1998            .with_external(&external, deployment.Localhost())
1999            .deploy(&mut deployment);
2000
2001        deployment.deploy().await.unwrap();
2002
2003        let mut out_recv_ref = nodes.connect(out_port_ref).await;
2004
2005        deployment.start().await.unwrap();
2006
2007        let mut ref_results = Vec::new();
2008        for _ in 0..2 {
2009            ref_results.push(out_recv_ref.next().await.unwrap());
2010        }
2011        ref_results.sort();
2012        // reduce(0..5) = 10, so 1+10=11, 2+10=12
2013        assert_eq!(ref_results, vec![11, 12]);
2014    }
2015}