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