1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
use std::sync::{
Arc,
atomic::{AtomicBool, Ordering},
};
use super::{CellValue, Watchable};
use crate::{
cell::{Cell, CellImmutable, CellMutable},
signal::Signal,
};
/// Extension trait for fallible transformations.
pub trait TryMapExt<T>: Watchable<T> {
/// Transform values with a fallible function.
///
/// Returns a `Cell<Result<U, E>>` that contains `Ok(value)` when the
/// transform succeeds, or `Err(error)` when it fails.
///
/// # Example
/// ```
/// use hyphae::{Cell, Mutable, TryMapExt, Gettable};
///
/// let source = Cell::new(10i32);
/// let parsed = source.try_map(|v| {
/// if *v > 0 {
/// Ok(v.to_string())
/// } else {
/// Err("must be positive")
/// }
/// });
///
/// assert_eq!(parsed.get(), Ok("10".to_string()));
/// ```
#[track_caller]
fn try_map<U, E, F>(&self, f: F) -> Cell<Result<U, E>, CellImmutable>
where
T: CellValue,
U: CellValue,
E: CellValue,
F: Fn(&T) -> Result<U, E> + Send + Sync + 'static,
Self: Clone + Send + Sync + 'static,
{
let initial = f(&self.get());
let derived = Cell::<Result<U, E>, CellMutable>::new(initial);
let derived = if let Some(name) = self.name() {
derived.with_name(format!("{}::try_map", name))
} else {
derived
};
let weak = derived.downgrade();
let first = Arc::new(AtomicBool::new(true));
let guard = self.subscribe(move |signal| {
if let Some(d) = weak.upgrade() {
match signal {
Signal::Value(value) => {
if first.swap(false, Ordering::SeqCst) {
return;
}
d.notify(Signal::value(f(value.as_ref())));
}
Signal::Complete => d.notify(Signal::Complete),
Signal::Error(e) => d.notify(Signal::Error(e.clone())),
}
}
});
derived.own(guard);
derived.lock()
}
}
impl<T, W: Watchable<T>> TryMapExt<T> for W {}