anchors 0.6.0

async incremental computations
Documentation
use crate::expert::{Anchor, AnchorExt, Engine};
use im::ordmap::DiffItem;
use im::OrdMap;

pub type Dict<K, V> = OrdMap<K, V>;

impl<E: Engine, K: Ord + Clone + PartialEq + 'static, V: Clone + PartialEq + 'static>
    Anchor<Dict<K, V>, E>
{
    pub fn filter<F: FnMut(&K, &V) -> bool + 'static>(&self, mut f: F) -> Anchor<Dict<K, V>, E> {
        self.filter_map(move |k, v| if f(k, v) { Some(v.clone()) } else { None })
    }

    pub fn map<F: FnMut(&K, &V) -> T + 'static, T: Clone + PartialEq + 'static>(
        &self,
        mut f: F,
    ) -> Anchor<Dict<K, T>, E> {
        self.filter_map(move |k, v| Some(f(k, v)))
    }

    pub fn filter_map<F: FnMut(&K, &V) -> Option<T> + 'static, T: Clone + PartialEq + 'static>(
        &self,
        mut f: F,
    ) -> Anchor<Dict<K, T>, E> {
        self.unordered_fold(Dict::new(), move |out, diff_item| {
            match diff_item {
                DiffItem::Add(k, v) => {
                    if let Some(new) = f(k, v) {
                        out.insert(k.clone(), new);
                        return true;
                    }
                }
                DiffItem::Update {
                    new: (k, v),
                    old: _,
                } => {
                    if let Some(new) = f(k, v) {
                        out.insert(k.clone(), new);
                        return true;
                    } else if out.contains_key(k) {
                        out.remove(k);
                        return true;
                    }
                }
                DiffItem::Remove(k, _v) => {
                    out.remove(k);
                    return true;
                }
            }
            false
        })
    }

    pub fn unordered_fold<
        T: PartialEq + Clone + 'static,
        F: for<'a> FnMut(&mut T, DiffItem<'a, K, V>) -> bool + 'static,
    >(
        &self,
        initial_state: T,
        mut f: F,
    ) -> Anchor<T, E> {
        let mut last_observation = Dict::new();
        self.map_mut(initial_state, move |mut out, this| {
            let mut did_update = false;
            for item in last_observation.diff(this) {
                if f(&mut out, item) {
                    did_update = true;
                }
            }
            last_observation = this.clone();
            did_update
        })
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn test_filter() {
        let mut engine = crate::singlethread::Engine::new();
        let mut dict = Dict::new();
        let (a, a_setter) = crate::expert::Var::new(dict.clone());
        let b = a.filter(|_, n| *n > 10);
        let b_out = engine.get(&b);
        assert_eq!(0, b_out.len());

        dict.insert("a".to_string(), 1);
        dict.insert("b".to_string(), 23);
        dict.insert("c".to_string(), 5);
        dict.insert("d".to_string(), 24);
        a_setter.set(dict.clone());
        let b_out = engine.get(&b);
        assert_eq!(2, b_out.len());
        assert_eq!(Some(&23), b_out.get("b"));
        assert_eq!(Some(&24), b_out.get("d"));

        dict.insert("a".to_string(), 25);
        dict.insert("b".to_string(), 5);
        dict.remove("d");
        dict.insert("e".to_string(), 50);
        a_setter.set(dict.clone());
        let b_out = engine.get(&b);
        assert_eq!(2, b_out.len());
        assert_eq!(Some(&25), b_out.get("a"));
        assert_eq!(Some(&50), b_out.get("e"));
    }

    #[test]
    fn test_map() {
        let mut engine = crate::singlethread::Engine::new();
        let mut dict = Dict::new();
        let (a, a_setter) = crate::expert::Var::new(dict.clone());
        let b = a.map(|_, n| *n + 1);
        let b_out = engine.get(&b);
        assert_eq!(0, b_out.len());

        dict.insert("a".to_string(), 1);
        dict.insert("b".to_string(), 2);
        dict.insert("c".to_string(), 3);
        dict.insert("d".to_string(), 4);
        a_setter.set(dict.clone());
        let b_out = engine.get(&b);
        assert_eq!(4, b_out.len());
        assert_eq!(Some(&2), b_out.get("a"));
        assert_eq!(Some(&3), b_out.get("b"));
        assert_eq!(Some(&4), b_out.get("c"));
        assert_eq!(Some(&5), b_out.get("d"));

        dict.insert("a".to_string(), 10);
        dict.insert("b".to_string(), 11);
        dict.remove("d");
        dict.insert("e".to_string(), 12);
        a_setter.set(dict.clone());
        let b_out = engine.get(&b);
        assert_eq!(4, b_out.len());
        assert_eq!(Some(&11), b_out.get("a"));
        assert_eq!(Some(&12), b_out.get("b"));
        assert_eq!(Some(&4), b_out.get("c"));
        assert_eq!(Some(&13), b_out.get("e"));
    }
}