~raph/immutable

8d98f255c66b59e3bdc80b539f089721b68a9c39 — Raph Levien 2 years ago 97045ac
Start of proxy objects

Allow mutation of values, actually producing deltas.
3 files changed, 164 insertions(+), 1 deletions(-)

M src/main.rs
A src/proxy.rs
M src/value.rs
M src/main.rs => src/main.rs +1 -0
@@ 11,6 11,7 @@ use druid::{
    BoxConstraints, HandlerCtx, Id, LayoutCtx, LayoutResult, ListenerCtx, Ui, UiMain, UiState, Widget,
};

pub mod proxy;
pub mod value;

use value::{Delta, DeltaEl, PathEl, Value};

A src/proxy.rs => src/proxy.rs +116 -0
@@ 0,0 1,116 @@
//! Proxy objects allowing mutation, producing a delta.

use crate::value::{Delta, DeltaEl, Error, Path, PathEl, Value};

/// A proxy object wrapping immutable data and allowing mutation.
///
/// This version is duck-typed, ie deals with generic value objects.
pub struct Proxy {
    // Invariant: original.apply(delta) = new
    // Not clear we actually need to keep original.
    original: Value,
    new: Value,
    delta: Vec<DeltaEl>,
}

pub struct ProxyInner<'a> {
    // Invariant: path is always valid
    proxy: &'a mut Proxy,
    path: Path,
    // Possible optimization: also hold value
}

impl Proxy {
    pub fn root(&mut self) -> ProxyInner {
        ProxyInner {
            proxy: self,
            path: Vec::new(),
        }
    }

    pub fn into_delta(self) -> Delta {
        self.delta
    }

    /// Apply a simple delta, recording it and updating the state.
    ///
    /// Note: will panic if delta is invalid.
    fn apply_delta_el(&mut self, delta_el: DeltaEl) -> Result<(), Error> {
        self.new = self.new.apply(&[delta_el.clone()])?;
        self.delta.push(delta_el);
        Ok(())
    }
}

impl<'a> ProxyInner<'a> {
    pub fn value(&self) -> &Value {
        self.proxy.new.access(&self.path).unwrap()
    }

    pub fn get(&mut self, el: impl Into<PathEl>) -> Option<ProxyInner> {
        let value = self.value();
        match el.into() {
            PathEl::List(ix) => {
                if value.as_list().map(|l| ix < l.len()).unwrap_or(false) {
                    let mut path = self.path.clone();
                    path.push(ix.into());
                    Some(ProxyInner {
                        proxy: self.proxy,
                        path,
                    })
                } else {
                    None
                }
            }
            PathEl::Map(key) => {
                if value.as_map().map(|m| m.contains_key(&key)).unwrap_or(false) {
                    let mut path = self.path.clone();
                    path.push(key.into());
                    Some(ProxyInner {
                        proxy: self.proxy,
                        path,
                    })
                } else {
                    None
                }
            }
        }
    }

    /// Update a key in a map to an optional new value (or delete the key).
    // Maybe use typestate pattern, can't fail if it's a map?
    pub fn update(&mut self, key: String, value: Option<Value>) -> Result<(), Error> {
        let mut path = self.path.clone();
        path.push(key.into());
        let delta_el = DeltaEl {
            path,
            new_value: value,
        };
        self.proxy.apply_delta_el(delta_el)
    }

    /// Update a key in a map to a new value.
    pub fn insert(&mut self, key: String, value: Value) -> Result<(), Error> {
        self.update(key, Some(value))
    }

    /// Delete a key in a map.
    ///
    /// TODO: should we return an error if the key doesn't exist?
    pub fn delete(&mut self, key: String) -> Result<(), Error> {
        self.update(key, None)
    }

    /// Set an array element to a new value.
    ///
    /// Can be used to push an element (if ix is the array length).
    pub fn set(&mut self, ix: usize, value: Value) -> Result<(), Error> {
        let mut path = self.path.clone();
        path.push(ix.into());
        let delta_el = DeltaEl {
            path,
            new_value: Some(value),
        };
        self.proxy.apply_delta_el(delta_el)
    }
}

M src/value.rs => src/value.rs +47 -1
@@ 36,6 36,7 @@ pub enum PathEl {
// TODO: we probably want &[PathEl] for most read-only cases.
// Actually there are a lot of tradeoffs, it could be ref-counted and even a linked list
// to make lifetimes / cloning easier.
// Also consider not using the name Path, it conflicts with std.
pub type Path = Vec<PathEl>;

/// One element of a delta - replaces a subtree at a specific location.


@@ 73,7 74,9 @@ impl Value {
    }

    /// Apply a delta, resulting in a new value.
    pub fn apply(&self, delta: &Delta) -> Result<Value, Error> {
    ///
    /// Maybe supply the delta as impl `AsRef`?
    pub fn apply(&self, delta: &[DeltaEl]) -> Result<Value, Error> {
        let mut result = self.clone();
        for el in delta {
            result = result.apply_delta_el(el)?;


@@ 133,6 136,38 @@ impl Value {
            Ok(self.clone())
        }
    }

    pub fn as_list(&self) -> Option<&[Value]> {
        if let ValueEnum::List(l) = self.0.deref() {
            Some(l)
        } else {
            None
        }
    }

    pub fn as_map(&self) -> Option<&HashMap<String, Value>> {
        if let ValueEnum::Map(m) = self.0.deref() {
            Some(m)
        } else {
            None
        }
    }

    pub fn as_f64(&self) -> Option<f64> {
        if let ValueEnum::Float(f) = self.0.deref() {
            Some(*f)
        } else {
            None
        }
    }

    pub fn as_str(&self) -> Option<&str> {
        if let ValueEnum::String(s) = self.0.deref() {
            Some(s)
        } else {
            None
        }
    }
}

impl From<String> for Value {


@@ 158,3 193,14 @@ impl From<HashMap<String, Value>> for Value {
        Value(Arc::new(ValueEnum::Map(m)))
    }
}

impl From<String> for PathEl {
    fn from(s: String) -> PathEl {
        PathEl::Map(s)
    }
}
impl From<usize> for PathEl {
    fn from(ix: usize) -> PathEl {
        PathEl::List(ix)
    }
}