From 427bda5d6f570049b02ca9301eb4e5da3b489ed6 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sun, 28 Aug 2016 19:05:43 -0700 Subject: [PATCH 01/15] react: add input cells and change their values --- config.json | 3 +- exercises/react/Cargo.lock | 4 +++ exercises/react/Cargo.toml | 3 ++ exercises/react/example.rs | 57 ++++++++++++++++++++++++++++++++++ exercises/react/src/lib.rs | 1 + exercises/react/tests/react.rs | 26 ++++++++++++++++ problems.md | 1 + 7 files changed, 94 insertions(+), 1 deletion(-) create mode 100644 exercises/react/Cargo.lock create mode 100644 exercises/react/Cargo.toml create mode 100644 exercises/react/example.rs create mode 120000 exercises/react/src/lib.rs create mode 100644 exercises/react/tests/react.rs diff --git a/config.json b/config.json index a90b1f05f..ef5f843b8 100644 --- a/config.json +++ b/config.json @@ -41,7 +41,8 @@ "parallel-letter-frequency", "rectangles", "forth", - "circular-buffer" + "circular-buffer", + "react" ], "deprecated": [ diff --git a/exercises/react/Cargo.lock b/exercises/react/Cargo.lock new file mode 100644 index 000000000..ac579264f --- /dev/null +++ b/exercises/react/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "react" +version = "0.0.0" + diff --git a/exercises/react/Cargo.toml b/exercises/react/Cargo.toml new file mode 100644 index 000000000..b6dae4439 --- /dev/null +++ b/exercises/react/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "react" +version = "0.0.0" diff --git a/exercises/react/example.rs b/exercises/react/example.rs new file mode 100644 index 000000000..4244f169a --- /dev/null +++ b/exercises/react/example.rs @@ -0,0 +1,57 @@ +// TODO: For now, lib is symlinked to example to ease local development. +// But the final plan is to provide a stub file once we know what the interface will be. + +pub trait Cell { + fn value(&self) -> &T; +} + +pub struct Reactor; + +pub struct InputCell { + val: T, +} + +pub struct Compute1Cell<'a, T: 'a, U, F: Fn(&T) -> U> { + compute: F, + cell: &'a Cell, + val: U, +} + +impl Reactor { + pub fn new() -> Reactor { + Reactor{} + } + + pub fn create_input(&self, initial: T) -> InputCell { + InputCell { + val: initial, + } + } + + pub fn create_compute1<'a, T, U, F>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> + where F: Fn(&T) -> U { + Compute1Cell { + val: compute(cell.value()), + cell: cell, + compute: compute, + } + } +} + +impl Cell for InputCell { + fn value(&self) -> &T { + &self.val + } +} + +impl InputCell { + pub fn set_value(&mut self, new_val: T) { + self.val = new_val; + } +} + +impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { + fn value(&self) -> &U { + &self.val + } +} diff --git a/exercises/react/src/lib.rs b/exercises/react/src/lib.rs new file mode 120000 index 000000000..85b5802c4 --- /dev/null +++ b/exercises/react/src/lib.rs @@ -0,0 +1 @@ +../example.rs \ No newline at end of file diff --git a/exercises/react/tests/react.rs b/exercises/react/tests/react.rs new file mode 100644 index 000000000..871c9ba45 --- /dev/null +++ b/exercises/react/tests/react.rs @@ -0,0 +1,26 @@ +extern crate react; + +#[allow(unused_mut)] + +// TODO: [ignore] tests + +use react::*; + +#[test] +fn set_value_of_input() { + let mut reactor = Reactor::new(); + let mut input = reactor.create_input(1); + assert_eq!(*input.value(), 1); + input.set_value(2); + assert_eq!(*input.value(), 2); +} + +#[test] +fn compute1_depending_on_input() { + let mut reactor = Reactor::new(); + let mut input = reactor.create_input(1); + let output = reactor.create_compute1(&input, |v| v + 1); + assert_eq!(*output.value(), 2); + //input.set_value(2); + //assert_eq!(*output.value(), 3); +} diff --git a/problems.md b/problems.md index 6fd8a35e9..1633b0800 100644 --- a/problems.md +++ b/problems.md @@ -75,3 +75,4 @@ parallel-letter-frequency | multi-threading rectangles | Enum, structs, traits, Lifetimes forth | Parser reimplementation circular-buffer | Buffer reimplementation, Generics +react | TODO From 8735dfcfe26319ab72d515f9326f160bda632ad9 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Thu, 1 Sep 2016 01:18:35 -0700 Subject: [PATCH 02/15] react: topics --- problems.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/problems.md b/problems.md index 1633b0800..64fd257f8 100644 --- a/problems.md +++ b/problems.md @@ -75,4 +75,4 @@ parallel-letter-frequency | multi-threading rectangles | Enum, structs, traits, Lifetimes forth | Parser reimplementation circular-buffer | Buffer reimplementation, Generics -react | TODO +react | Lifetimes, generics, closures From a47d0f177e9142e29ea08459d827c9eb5807109e Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Mon, 29 Aug 2016 02:49:20 -0700 Subject: [PATCH 03/15] in case you were wondering, this doesn't live long enough because the value returned by compute only lives for the stack frame, I believe. --- exercises/react/example.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index 4244f169a..92e734feb 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -52,6 +52,6 @@ impl InputCell { impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { fn value(&self) -> &U { - &self.val + &(self.compute)(self.cell.value()) } } From 00c21014ee28ec5fe5d01306bf5bbb6e97503d46 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Mon, 29 Aug 2016 02:49:56 -0700 Subject: [PATCH 04/15] Revert "in case you were wondering, this doesn't live long enough" This reverts commit 4d956196d7f91048a1559e8060007e80729932f2. --- exercises/react/example.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index 92e734feb..4244f169a 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -52,6 +52,6 @@ impl InputCell { impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { fn value(&self) -> &U { - &(self.compute)(self.cell.value()) + &self.val } } From eff25018e05e9707da46f6d57fe8524984cc8bae Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Mon, 29 Aug 2016 03:02:40 -0700 Subject: [PATCH 05/15] Maybe I can have the reactor track all compute cells? NOPE Lifetime, because ComputeCell needs to live at least as long a the Reactor storing it. I don't think this is an insurmountable problem, but I'm not sure where to put the lifetime annotations now --- exercises/react/example.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index 4244f169a..678d95a21 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -5,7 +5,13 @@ pub trait Cell { fn value(&self) -> &T; } -pub struct Reactor; +trait Propagatable { + fn propagate(&mut self); +} + +pub struct Reactor { + cells: Vec>, +} pub struct InputCell { val: T, @@ -19,7 +25,9 @@ pub struct Compute1Cell<'a, T: 'a, U, F: Fn(&T) -> U> { impl Reactor { pub fn new() -> Reactor { - Reactor{} + Reactor{ + cells: Vec::new(), + } } pub fn create_input(&self, initial: T) -> InputCell { @@ -28,13 +36,15 @@ impl Reactor { } } - pub fn create_compute1<'a, T, U, F>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> + pub fn create_compute1<'a, T, U, F>(&mut self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> where F: Fn(&T) -> U { - Compute1Cell { + let cell = Compute1Cell { val: compute(cell.value()), cell: cell, compute: compute, - } + }; + self.cells.push(Box::new(cell)); + cell } } @@ -55,3 +65,9 @@ impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { &self.val } } + +impl <'a, T, U, F: Fn(&T) -> U> Propagatable for Compute1Cell<'a, T, U, F> { + fn propagate(&mut self) { + self.val = (self.compute)(self.cell.value()); + } +} From 34fd901c752002e3ea3008508115d4a4398e004e Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Mon, 29 Aug 2016 03:13:25 -0700 Subject: [PATCH 06/15] Revert "Maybe I can have the reactor track all compute cells? NOPE" This reverts commit 13886af694f7d98c7307bcd66513f5c876a0b609. --- exercises/react/example.rs | 26 +++++--------------------- 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index 678d95a21..4244f169a 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -5,13 +5,7 @@ pub trait Cell { fn value(&self) -> &T; } -trait Propagatable { - fn propagate(&mut self); -} - -pub struct Reactor { - cells: Vec>, -} +pub struct Reactor; pub struct InputCell { val: T, @@ -25,9 +19,7 @@ pub struct Compute1Cell<'a, T: 'a, U, F: Fn(&T) -> U> { impl Reactor { pub fn new() -> Reactor { - Reactor{ - cells: Vec::new(), - } + Reactor{} } pub fn create_input(&self, initial: T) -> InputCell { @@ -36,15 +28,13 @@ impl Reactor { } } - pub fn create_compute1<'a, T, U, F>(&mut self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> + pub fn create_compute1<'a, T, U, F>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> where F: Fn(&T) -> U { - let cell = Compute1Cell { + Compute1Cell { val: compute(cell.value()), cell: cell, compute: compute, - }; - self.cells.push(Box::new(cell)); - cell + } } } @@ -65,9 +55,3 @@ impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { &self.val } } - -impl <'a, T, U, F: Fn(&T) -> U> Propagatable for Compute1Cell<'a, T, U, F> { - fn propagate(&mut self) { - self.val = (self.compute)(self.cell.value()); - } -} From b284baf640b5a63bf3f33114115b0ea736fe3e4e Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Wed, 31 Aug 2016 06:38:28 -0700 Subject: [PATCH 07/15] let's have cells and epochs and the cell caches a value...! NOPE, then you can't return anything from a compute cell --- exercises/react/example.rs | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index 4244f169a..b545fe79a 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -1,20 +1,26 @@ // TODO: For now, lib is symlinked to example to ease local development. // But the final plan is to provide a stub file once we know what the interface will be. + +use std::cell::Cell as MutCell; + pub trait Cell { fn value(&self) -> &T; + fn epoch(&self) -> usize; } pub struct Reactor; pub struct InputCell { val: T, + epoch: usize, } -pub struct Compute1Cell<'a, T: 'a, U, F: Fn(&T) -> U> { +pub struct Compute1Cell<'a, T: 'a, U: Copy, F: Fn(&T) -> U> { compute: F, cell: &'a Cell, - val: U, + epoch: MutCell, + val: MutCell, } impl Reactor { @@ -25,14 +31,15 @@ impl Reactor { pub fn create_input(&self, initial: T) -> InputCell { InputCell { val: initial, + epoch: 0, } } - pub fn create_compute1<'a, T, U, F>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> - where F: Fn(&T) -> U { + pub fn create_compute1<'a, T, U: Copy, F: Fn(&T) -> U>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> { Compute1Cell { - val: compute(cell.value()), + val: MutCell::new(compute(cell.value())), cell: cell, + epoch: MutCell::new(cell.epoch()), compute: compute, } } @@ -42,6 +49,10 @@ impl Cell for InputCell { fn value(&self) -> &T { &self.val } + + fn epoch(&self) -> usize { + self.epoch + } } impl InputCell { @@ -50,8 +61,16 @@ impl InputCell { } } -impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { +impl <'a, T, U: Copy, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { fn value(&self) -> &U { - &self.val + if self.epoch() < self.cell.epoch() { + self.epoch.set(self.cell.epoch()); + self.val.set((self.compute)(self.cell.value())); + } + &self.val.get().clone() + } + + fn epoch(&self) -> usize { + self.epoch.get() } } From ccda60d2eb9c6cd5ae73dcb78d992c7e57c59301 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Wed, 31 Aug 2016 06:39:33 -0700 Subject: [PATCH 08/15] Revert "let's have cells and epochs and the cell caches a value...!" This reverts commit 88f2463cd1edc36db97fe7c3ceadc7ffb038c479. --- exercises/react/example.rs | 33 +++++++-------------------------- 1 file changed, 7 insertions(+), 26 deletions(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index b545fe79a..4244f169a 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -1,26 +1,20 @@ // TODO: For now, lib is symlinked to example to ease local development. // But the final plan is to provide a stub file once we know what the interface will be. - -use std::cell::Cell as MutCell; - pub trait Cell { fn value(&self) -> &T; - fn epoch(&self) -> usize; } pub struct Reactor; pub struct InputCell { val: T, - epoch: usize, } -pub struct Compute1Cell<'a, T: 'a, U: Copy, F: Fn(&T) -> U> { +pub struct Compute1Cell<'a, T: 'a, U, F: Fn(&T) -> U> { compute: F, cell: &'a Cell, - epoch: MutCell, - val: MutCell, + val: U, } impl Reactor { @@ -31,15 +25,14 @@ impl Reactor { pub fn create_input(&self, initial: T) -> InputCell { InputCell { val: initial, - epoch: 0, } } - pub fn create_compute1<'a, T, U: Copy, F: Fn(&T) -> U>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> { + pub fn create_compute1<'a, T, U, F>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> + where F: Fn(&T) -> U { Compute1Cell { - val: MutCell::new(compute(cell.value())), + val: compute(cell.value()), cell: cell, - epoch: MutCell::new(cell.epoch()), compute: compute, } } @@ -49,10 +42,6 @@ impl Cell for InputCell { fn value(&self) -> &T { &self.val } - - fn epoch(&self) -> usize { - self.epoch - } } impl InputCell { @@ -61,16 +50,8 @@ impl InputCell { } } -impl <'a, T, U: Copy, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { +impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { fn value(&self) -> &U { - if self.epoch() < self.cell.epoch() { - self.epoch.set(self.cell.epoch()); - self.val.set((self.compute)(self.cell.value())); - } - &self.val.get().clone() - } - - fn epoch(&self) -> usize { - self.epoch.get() + &self.val } } From b257e7eadd2e19af9d527fef2f9eb008dedb5076 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Wed, 31 Aug 2016 21:42:53 -0700 Subject: [PATCH 09/15] react: try get/get_mut interface --- exercises/react/example.rs | 79 +++++++++++++++++++--------------- exercises/react/tests/react.rs | 26 +++++++---- 2 files changed, 62 insertions(+), 43 deletions(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index 4244f169a..8ceb8406f 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -1,57 +1,68 @@ // TODO: For now, lib is symlinked to example to ease local development. // But the final plan is to provide a stub file once we know what the interface will be. -pub trait Cell { - fn value(&self) -> &T; -} - -pub struct Reactor; +pub type CellID = usize; -pub struct InputCell { +pub struct Cell<'a, T: Copy> { val: T, + dependents: Vec, + cell_type: CellType<'a, T>, +} + +pub enum CellType<'a, T: Copy> { + Input, + Compute(Vec, Box T + 'a>), } -pub struct Compute1Cell<'a, T: 'a, U, F: Fn(&T) -> U> { - compute: F, - cell: &'a Cell, - val: U, +pub struct Reactor<'a, T: Copy> { + cells: Vec>, } -impl Reactor { - pub fn new() -> Reactor { - Reactor{} +impl <'a, T: Copy> Reactor<'a, T> { + pub fn new() -> Self { + Reactor{ + cells: Vec::new(), + } } - pub fn create_input(&self, initial: T) -> InputCell { - InputCell { + pub fn create_input(&mut self, initial: T) -> CellID { + self.cells.push(Cell { val: initial, - } + dependents: Vec::new(), + cell_type: CellType::Input, + }); + self.cells.len() - 1 } - pub fn create_compute1<'a, T, U, F>(&self, cell: &'a Cell, compute: F) -> Compute1Cell<'a, T, U, F> - where F: Fn(&T) -> U { - Compute1Cell { - val: compute(cell.value()), - cell: cell, - compute: compute, - } + pub fn create_compute T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> CellID { + let inputs: Vec<_> = dependencies.iter().map(|&id| self.get(id).unwrap().value()).collect(); + let initial = compute_func(&inputs); + self.cells.push(Cell { + val: initial, + dependents: Vec::new(), + cell_type: CellType::Compute(dependencies.iter().cloned().collect(), Box::new(compute_func)), + }); + self.cells.len() - 1 } -} -impl Cell for InputCell { - fn value(&self) -> &T { - &self.val + pub fn get(&self, id: CellID) -> Option<&Cell<'a, T>> { + self.cells.get(id) } -} -impl InputCell { - pub fn set_value(&mut self, new_val: T) { - self.val = new_val; + pub fn get_mut(&mut self, id: CellID) -> Option<&mut Cell<'a, T>> { + self.cells.get_mut(id) } } -impl <'a, T, U, F: Fn(&T) -> U> Cell for Compute1Cell<'a, T, U, F> { - fn value(&self) -> &U { - &self.val +impl <'a, T: Copy> Cell<'a, T> { + pub fn value(&self) -> T { + self.val + } + + pub fn set_value(&mut self, new_val: T) { + match self.cell_type { + CellType::Input => self.val = new_val, + CellType::Compute(_, _) => panic!("Can't set compute value directly"), + } } } diff --git a/exercises/react/tests/react.rs b/exercises/react/tests/react.rs index 871c9ba45..657b1c3d5 100644 --- a/exercises/react/tests/react.rs +++ b/exercises/react/tests/react.rs @@ -1,26 +1,34 @@ extern crate react; +use std::fmt::Debug; + #[allow(unused_mut)] // TODO: [ignore] tests use react::*; +fn assert_cell_value<'a, T: Copy + PartialEq + Debug>(reactor: &Reactor<'a, T>, id: CellID, expected: T) { + let cell = reactor.get(id).unwrap(); + assert_eq!(cell.value(), expected); +} + #[test] fn set_value_of_input() { let mut reactor = Reactor::new(); - let mut input = reactor.create_input(1); - assert_eq!(*input.value(), 1); - input.set_value(2); - assert_eq!(*input.value(), 2); + let input = reactor.create_input(1); + assert_cell_value(&reactor, input, 1); + { + let mut cell = reactor.get_mut(input).unwrap(); + cell.set_value(2); + } + assert_cell_value(&reactor, input, 2); } #[test] fn compute1_depending_on_input() { let mut reactor = Reactor::new(); - let mut input = reactor.create_input(1); - let output = reactor.create_compute1(&input, |v| v + 1); - assert_eq!(*output.value(), 2); - //input.set_value(2); - //assert_eq!(*output.value(), 3); + let input = reactor.create_input(1); + let output = reactor.create_compute(&vec![input], |v| v[0] + 1); + assert_cell_value(&reactor, output, 2); } From f0d4de34f5a83454b21e832e17890d1ef296f432 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Wed, 31 Aug 2016 23:17:07 -0700 Subject: [PATCH 10/15] react: give up on get and get_mut in favor of value and set_value get and get_mut had too many problems: once you set_value on a cell, how does it know to propagate its changes to its dependents? It either needs references to its dependents or its reactor. --- exercises/react/example.rs | 157 ++++++++++++++++++++++++------ exercises/react/tests/react.rs | 172 +++++++++++++++++++++++++++++---- 2 files changed, 281 insertions(+), 48 deletions(-) diff --git a/exercises/react/example.rs b/exercises/react/example.rs index 8ceb8406f..8fdd8eeb9 100644 --- a/exercises/react/example.rs +++ b/exercises/react/example.rs @@ -1,24 +1,40 @@ -// TODO: For now, lib is symlinked to example to ease local development. -// But the final plan is to provide a stub file once we know what the interface will be. +use std::collections::HashMap; pub type CellID = usize; +pub type CallbackID = usize; -pub struct Cell<'a, T: Copy> { - val: T, +struct Cell<'a, T: Copy> { + value: T, + last_value: T, dependents: Vec, cell_type: CellType<'a, T>, + callbacks_issued: usize, + callbacks: HashMap () + 'a>>, } -pub enum CellType<'a, T: Copy> { +enum CellType<'a, T: Copy> { Input, Compute(Vec, Box T + 'a>), } +impl <'a, T: Copy> Cell<'a, T> { + fn new(initial: T, cell_type: CellType<'a, T>) -> Self { + Cell { + value: initial, + last_value: initial, + dependents: Vec::new(), + cell_type: cell_type, + callbacks_issued: 0, + callbacks: HashMap::new(), + } + } +} + pub struct Reactor<'a, T: Copy> { cells: Vec>, } -impl <'a, T: Copy> Reactor<'a, T> { +impl <'a, T: Copy + PartialEq> Reactor<'a, T> { pub fn new() -> Self { Reactor{ cells: Vec::new(), @@ -26,43 +42,122 @@ impl <'a, T: Copy> Reactor<'a, T> { } pub fn create_input(&mut self, initial: T) -> CellID { - self.cells.push(Cell { - val: initial, - dependents: Vec::new(), - cell_type: CellType::Input, - }); + self.cells.push(Cell::new(initial, CellType::Input)); self.cells.len() - 1 } - pub fn create_compute T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> CellID { - let inputs: Vec<_> = dependencies.iter().map(|&id| self.get(id).unwrap().value()).collect(); + pub fn create_compute T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> Result { + let new_id = self.cells.len(); + for &id in dependencies { + match self.cells.get_mut(id) { + Some(c) => c.dependents.push(new_id), + None => return Err("Nonexistent input"), + } + } + let inputs: Vec<_> = dependencies.iter().map(|&id| self.value(id).unwrap()).collect(); let initial = compute_func(&inputs); - self.cells.push(Cell { - val: initial, - dependents: Vec::new(), - cell_type: CellType::Compute(dependencies.iter().cloned().collect(), Box::new(compute_func)), - }); - self.cells.len() - 1 + self.cells.push(Cell::new(initial, CellType::Compute(dependencies.iter().cloned().collect(), Box::new(compute_func)))); + Ok(new_id) } - pub fn get(&self, id: CellID) -> Option<&Cell<'a, T>> { - self.cells.get(id) + pub fn value(&self, id: CellID) -> Option { + self.cells.get(id).map(|c| c.value) } - pub fn get_mut(&mut self, id: CellID) -> Option<&mut Cell<'a, T>> { - self.cells.get_mut(id) + pub fn set_value(&mut self, id: CellID, new_value: T) -> Result<(), &'static str> { + match self.cells.get_mut(id) { + Some(c) => match c.cell_type { + CellType::Input => { + c.value = new_value; + Ok(c.dependents.clone()) + }, + CellType::Compute(_, _) => Err("Can't set compute cell value directly"), + }, + None => Err("Can't set nonexistent cell"), + }.map(|deps| { + for &d in deps.iter() { + self.update_dependent(d); + } + // We can only fire callbacks after all dependents have updated. + // So we can't combine this for loop with the one above! + for d in deps { + self.fire_callbacks(d); + } + }) } -} -impl <'a, T: Copy> Cell<'a, T> { - pub fn value(&self) -> T { - self.val + pub fn add_callback () + 'a>(&mut self, id: CellID, callback: F) -> Result { + match self.cells.get_mut(id) { + Some(c) => { + c.callbacks_issued += 1; + c.callbacks.insert(c.callbacks_issued, Box::new(callback)); + Ok(c.callbacks_issued) + }, + None => Err("Can't add callback to nonexistent cell"), + } + } + + pub fn remove_callback(&mut self, cell: CellID, callback: CallbackID) -> Result<(), &'static str> { + match self.cells.get_mut(cell) { + Some(c) => match c.callbacks.remove(&callback) { + Some(_) => Ok(()), + None => Err("Can't remove nonexistent callback"), + }, + None => Err("Can't remove callback from nonexistent cell"), + } } - pub fn set_value(&mut self, new_val: T) { - match self.cell_type { - CellType::Input => self.val = new_val, - CellType::Compute(_, _) => panic!("Can't set compute value directly"), + fn update_dependent(&mut self, id: CellID) { + let (new_value, dependents) = { + // This block limits the scope of the self.cells borrow. + // This is necessary becaue we borrow it mutably below. + let (dependencies, f, dependents) = match self.cells.get(id) { + Some(c) => match c.cell_type { + CellType::Input => panic!("Input cell can't be a dependent"), + CellType::Compute(ref dependencies, ref f) => (dependencies, f, c.dependents.clone()), + }, + None => panic!("Cell to update disappeared while querying"), + }; + let inputs: Vec<_> = dependencies.iter().map(|&id| self.value(id).unwrap()).collect(); + (f(&inputs), dependents) + }; + + match self.cells.get_mut(id) { + Some(c) => { + if c.value == new_value { + // No change here, we don't need to update our dependents. + // (It wouldn't hurt to, but it would be unnecessary work) + return; + } + c.value = new_value; + }, + None => panic!("Cell to update disappeared while updating"), + } + + for d in dependents { + self.update_dependent(d); + } + } + + fn fire_callbacks(&mut self, id: CellID) { + let dependents = match self.cells.get_mut(id) { + Some(c) => { + if c.value == c.last_value { + // Value hasn't changed since last callback fire. + // We thus shouldn't fire the callbacks. + return + } + for cb in c.callbacks.values_mut() { + cb(c.value); + } + c.last_value = c.value; + c.dependents.clone() + }, + None => panic!("Callback cell disappeared"), + }; + + for d in dependents { + self.fire_callbacks(d); } } } diff --git a/exercises/react/tests/react.rs b/exercises/react/tests/react.rs index 657b1c3d5..8ea1efc37 100644 --- a/exercises/react/tests/react.rs +++ b/exercises/react/tests/react.rs @@ -1,34 +1,172 @@ extern crate react; -use std::fmt::Debug; +use react::*; -#[allow(unused_mut)] +#[test] +fn input_cells_have_a_value() { + let mut reactor = Reactor::new(); + let input = reactor.create_input(10); + assert_eq!(reactor.value(input).unwrap(), 10); +} -// TODO: [ignore] tests +#[test] +#[ignore] +fn an_input_cells_value_can_be_set() { + let mut reactor = Reactor::new(); + let input = reactor.create_input(4); + assert!(reactor.set_value(input, 20).is_ok()); + assert_eq!(reactor.value(input).unwrap(), 20); +} -use react::*; +#[test] +#[ignore] +fn compute_cells_calculate_initial_value() { + let mut reactor = Reactor::new(); + let input = reactor.create_input(1); + let output = reactor.create_compute(&vec![input], |v| v[0] + 1).unwrap(); + assert_eq!(reactor.value(output).unwrap(), 2); +} -fn assert_cell_value<'a, T: Copy + PartialEq + Debug>(reactor: &Reactor<'a, T>, id: CellID, expected: T) { - let cell = reactor.get(id).unwrap(); - assert_eq!(cell.value(), expected); +#[test] +#[ignore] +fn compute_cells_take_inputs_in_the_right_order() { + let mut reactor = Reactor::new(); + let one = reactor.create_input(1); + let two = reactor.create_input(2); + let output = reactor.create_compute(&vec![one, two], |v| v[0] + v[1] * 10).unwrap(); + assert_eq!(reactor.value(output).unwrap(), 21); } #[test] -fn set_value_of_input() { +#[ignore] +fn compute_cells_update_value_when_dependencies_are_changed() { let mut reactor = Reactor::new(); let input = reactor.create_input(1); - assert_cell_value(&reactor, input, 1); - { - let mut cell = reactor.get_mut(input).unwrap(); - cell.set_value(2); - } - assert_cell_value(&reactor, input, 2); + let output = reactor.create_compute(&vec![input], |v| v[0] + 1).unwrap(); + assert_eq!(reactor.value(output).unwrap(), 2); + assert!(reactor.set_value(input, 3).is_ok()); + assert_eq!(reactor.value(output).unwrap(), 4); } #[test] -fn compute1_depending_on_input() { +#[ignore] +fn compute_cells_can_depend_on_other_compute_cells() { let mut reactor = Reactor::new(); let input = reactor.create_input(1); - let output = reactor.create_compute(&vec![input], |v| v[0] + 1); - assert_cell_value(&reactor, output, 2); + let times_two = reactor.create_compute(&vec![input], |v| v[0] * 2).unwrap(); + let times_thirty = reactor.create_compute(&vec![input], |v| v[0] * 30).unwrap(); + let output = reactor.create_compute(&vec![times_two, times_thirty], |v| v[0] + v[1]).unwrap(); + assert_eq!(reactor.value(output).unwrap(), 32); + assert!(reactor.set_value(input, 3).is_ok()); + assert_eq!(reactor.value(output).unwrap(), 96); +} + +#[test] +#[ignore] +fn compute_cells_fire_callbacks() { + // This is a bit awkward, but the closure mutably borrows `values`. + // So we have to end its borrow by taking reactor out of scope. + let mut values = Vec::new(); + { + let mut reactor = Reactor::new(); + let input = reactor.create_input(1); + let output = reactor.create_compute(&vec![input], |v| v[0] + 1).unwrap(); + assert!(reactor.add_callback(output, |v| values.push(v)).is_ok()); + assert!(reactor.set_value(input, 3).is_ok()); + } + assert_eq!(values, vec![4]); +} + +#[test] +#[ignore] +fn callbacks_only_fire_on_change() { + let mut values = Vec::new(); + { + let mut reactor = Reactor::new(); + let input = reactor.create_input(1); + let output = reactor.create_compute(&vec![input], |v| if v[0] < 3 { 111 } else { 222 }).unwrap(); + assert!(reactor.add_callback(output, |v| values.push(v)).is_ok()); + assert!(reactor.set_value(input, 2).is_ok()); + assert!(reactor.set_value(input, 4).is_ok()); + } + assert_eq!(values, vec![222]); +} + +#[test] +#[ignore] +fn callbacks_can_be_added_and_removed() { + let mut values1 = Vec::new(); + let mut values2 = Vec::new(); + let mut values3 = Vec::new(); + { + let mut reactor = Reactor::new(); + let input = reactor.create_input(11); + let output = reactor.create_compute(&vec![input], |v| v[0] + 1).unwrap(); + let callback = reactor.add_callback(output, |v| values1.push(v)).unwrap(); + assert!(reactor.add_callback(output, |v| values2.push(v)).is_ok()); + assert!(reactor.set_value(input, 31).is_ok()); + assert!(reactor.remove_callback(output, callback).is_ok()); + assert!(reactor.add_callback(output, |v| values3.push(v)).is_ok()); + assert!(reactor.set_value(input, 41).is_ok()); + } + assert_eq!(values1, vec![32]); + assert_eq!(values2, vec![32, 42]); + assert_eq!(values3, vec![42]); +} + +#[test] +#[ignore] +fn removing_a_callback_multiple_times_doesnt_interfere_with_other_callbacks() { + let mut values1 = Vec::new(); + let mut values2 = Vec::new(); + { + let mut reactor = Reactor::new(); + let input = reactor.create_input(1); + let output = reactor.create_compute(&vec![input], |v| v[0] + 1).unwrap(); + let callback = reactor.add_callback(output, |v| values1.push(v)).unwrap(); + assert!(reactor.add_callback(output, |v| values2.push(v)).is_ok()); + // We want the first remove to be Ok, but we don't care about the others. + assert!(reactor.remove_callback(output, callback).is_ok()); + for _ in 1..5 { + let _ = reactor.remove_callback(output, callback); + } + assert!(reactor.set_value(input, 2).is_ok()); + } + assert_eq!(values1, Vec::new()); + assert_eq!(values2, vec![3]); +} + +#[test] +#[ignore] +fn callbacks_should_only_be_called_once_even_if_multiple_dependencies_change() { + let mut values = Vec::new(); + { + let mut reactor = Reactor::new(); + let input = reactor.create_input(1); + let plus_one = reactor.create_compute(&vec![input], |v| v[0] + 1).unwrap(); + let minus_one1 = reactor.create_compute(&vec![input], |v| v[0] - 1).unwrap(); + let minus_one2 = reactor.create_compute(&vec![minus_one1], |v| v[0] - 1).unwrap(); + let output = reactor.create_compute(&vec![plus_one, minus_one2], |v| v[0] * v[1]).unwrap(); + assert!(reactor.add_callback(output, |v| values.push(v)).is_ok()); + assert!(reactor.set_value(input, 4).is_ok()); + } + assert_eq!(values, vec![10]); +} + +#[test] +#[ignore] +fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt_change() { + let mut values = Vec::new(); + { + let mut reactor = Reactor::new(); + let input = reactor.create_input(1); + let plus_one = reactor.create_compute(&vec![input], |v| v[0] + 1).unwrap(); + let minus_one = reactor.create_compute(&vec![input], |v| v[0] - 1).unwrap(); + let always_two = reactor.create_compute(&vec![plus_one, minus_one], |v| v[0] - v[1]).unwrap(); + assert!(reactor.add_callback(always_two, |v| values.push(v)).is_ok()); + for i in 2..5 { + assert!(reactor.set_value(input, i).is_ok()); + } + } + assert_eq!(values, Vec::new()); } From 3a64f1ff2cb4831dede6364ae4a2fd071cb46ef9 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Thu, 1 Sep 2016 01:13:09 -0700 Subject: [PATCH 11/15] react: stub --- exercises/react/src/lib.rs | 92 +++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) mode change 120000 => 100644 exercises/react/src/lib.rs diff --git a/exercises/react/src/lib.rs b/exercises/react/src/lib.rs deleted file mode 120000 index 85b5802c4..000000000 --- a/exercises/react/src/lib.rs +++ /dev/null @@ -1 +0,0 @@ -../example.rs \ No newline at end of file diff --git a/exercises/react/src/lib.rs b/exercises/react/src/lib.rs new file mode 100644 index 000000000..a57c6beab --- /dev/null +++ b/exercises/react/src/lib.rs @@ -0,0 +1,91 @@ +#[allow(unused_variables)] + +// Because these are passed without & to some functions, +// it will probably be necessary for these two types to be Copy. +pub type CellID = (); +pub type CallbackID = (); + +pub struct Reactor { + // Just so that the compiler doesn't complain about an unused type parameter. + // You probably want to delete this field. + dummy: T, +} + +// You are guaranteed that Reactor will only be tested against types that are Copy + PartialEq. +impl <'a, T: Copy + PartialEq> Reactor { + pub fn new() -> Self { + unimplemented!() + } + + // Creates an input cell with the specified initial value, returning its ID. + pub fn create_input(&mut self, initial: T) -> CellID { + unimplemented!() + } + + // Creates a compute cell with the specified dependencies and compute function. + // The compute function is expected to take in its arguments in the same order as specified in + // `dependencies`. + // You do not need to reject compute functions that expect more arguments than there are + // dependencies (how would you check for this, anyway?). + // + // Return an Err (and you can change the error type) if any dependency doesn't exist. + // + // Notice that there is no way to *remove* a cell. + // This means that you may assume, without checking, that if the dependencies exist at creation + // time they will continue to exist as long as the Reactor exists. + pub fn create_compute T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> Result { + unimplemented!() + } + + // Retrieves the current value of the cell, or None if the cell does not exist. + // + // You may wonder whether it is possible to implement `get(&self, id: CellID) -> Option<&Cell>` + // and have a `value(&self)` method on `Cell`. + // + // It turns out this introduces a significant amount of extra complexity to this exercise. + // We chose not to cover this here, since this exercise is probably enough work as-is. + pub fn value(&self, id: CellID) -> Option { + unimplemented!() + } + + // Sets the value of the specified input cell. + // + // Return an Err (and you can change the error type) if the cell does not exist, or the + // specified cell is a compute cell, since compute cells cannot have their values directly set. + // + // Similarly, you may wonder about `get_mut(&mut self, id: CellID) -> Option<&mut Cell>`, with + // a `set_value(&mut self, new_value: T)` method on `Cell`. + // + // As before, that turned out to add too much extra complexity. + pub fn set_value(&mut self, id: CellID, new_value: T) -> Result<(), ()> { + unimplemented!() + } + + // Adds a callback to the specified compute cell. + // + // Return an Err (and you can change the error type) if the cell does not exist. + // + // Callbacks on input cells will not be tested. + // + // The semantics of callbacks (as will be tested): + // For a single set_value call, each compute cell's callbacks should each be called: + // * Zero times if the compute cell's value did not change as a result of the set_value call. + // * Exactly once if the compute cell's value changed as a result of the set_value call. + // The value passed to the callback should be the final value of the compute cell after the + // set_value call. + pub fn add_callback () + 'a>(&mut self, id: CellID, callback: F) -> Result { + unimplemented!() + } + + // Removes the specified callback, using an ID returned from add_callback. + // + // Return an Err (and you can change the error type) if the cell does not exist. + // + // If the callback does not exist (or has already been removed), it is up to you whether to + // return Ok(()) or an error. This should not interfere with other callbacks. + // + // A removed callback should no longer be called. + pub fn remove_callback(&mut self, cell: CellID, callback: CallbackID) -> Result<(), ()> { + unimplemented!() + } +} From 18b1a767b0fcc4185cd27bfae89fd8f9fd68103e Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Thu, 1 Sep 2016 03:10:58 -0700 Subject: [PATCH 12/15] react: add error tests --- exercises/react/tests/react.rs | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/exercises/react/tests/react.rs b/exercises/react/tests/react.rs index 8ea1efc37..d9baf5c57 100644 --- a/exercises/react/tests/react.rs +++ b/exercises/react/tests/react.rs @@ -18,6 +18,14 @@ fn an_input_cells_value_can_be_set() { assert_eq!(reactor.value(input).unwrap(), 20); } +#[test] +#[ignore] +fn error_setting_a_nonexistent_input_cell() { + let mut dummy_reactor = Reactor::new(); + let input = dummy_reactor.create_input(1); + assert!(Reactor::new().set_value(input, 0).is_err()); +} + #[test] #[ignore] fn compute_cells_calculate_initial_value() { @@ -37,6 +45,14 @@ fn compute_cells_take_inputs_in_the_right_order() { assert_eq!(reactor.value(output).unwrap(), 21); } +#[test] +#[ignore] +fn error_creating_compute_cell_if_input_doesnt_exist() { + let mut dummy_reactor = Reactor::new(); + let input = dummy_reactor.create_input(1); + assert!(Reactor::new().create_compute(&vec![input], |_| 0).is_err()); +} + #[test] #[ignore] fn compute_cells_update_value_when_dependencies_are_changed() { @@ -61,6 +77,15 @@ fn compute_cells_can_depend_on_other_compute_cells() { assert_eq!(reactor.value(output).unwrap(), 96); } +#[test] +#[ignore] +fn error_setting_a_compute_cell() { + let mut reactor = Reactor::new(); + let input = reactor.create_input(1); + let output = reactor.create_compute(&vec![input], |_| 0).unwrap(); + assert!(reactor.set_value(output, 3).is_err()); +} + #[test] #[ignore] fn compute_cells_fire_callbacks() { @@ -77,6 +102,15 @@ fn compute_cells_fire_callbacks() { assert_eq!(values, vec![4]); } +#[test] +#[ignore] +fn error_adding_callback_to_nonexistent_cell() { + let mut dummy_reactor = Reactor::new(); + let input = dummy_reactor.create_input(1); + let output = dummy_reactor.create_compute(&vec![input], |_| 0).unwrap(); + assert!(Reactor::new().add_callback(output, |_: usize| println!("hi")).is_err()); +} + #[test] #[ignore] fn callbacks_only_fire_on_change() { From b4996d900b50c17dbff1c5dc5499e4b29dbb2057 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Thu, 1 Sep 2016 10:01:01 -0700 Subject: [PATCH 13/15] react: RFC: remove the input lifetime from the stub??? This will make the exercise harder. Students will have to figure out that they need to annotate the Fn and FnMut types. (Or maybe they'll be more clever than I was...) --- exercises/react/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exercises/react/src/lib.rs b/exercises/react/src/lib.rs index a57c6beab..757c821f2 100644 --- a/exercises/react/src/lib.rs +++ b/exercises/react/src/lib.rs @@ -12,7 +12,7 @@ pub struct Reactor { } // You are guaranteed that Reactor will only be tested against types that are Copy + PartialEq. -impl <'a, T: Copy + PartialEq> Reactor { +impl Reactor { pub fn new() -> Self { unimplemented!() } @@ -33,7 +33,7 @@ impl <'a, T: Copy + PartialEq> Reactor { // Notice that there is no way to *remove* a cell. // This means that you may assume, without checking, that if the dependencies exist at creation // time they will continue to exist as long as the Reactor exists. - pub fn create_compute T + 'a>(&mut self, dependencies: &[CellID], compute_func: F) -> Result { + pub fn create_compute T>(&mut self, dependencies: &[CellID], compute_func: F) -> Result { unimplemented!() } @@ -73,7 +73,7 @@ impl <'a, T: Copy + PartialEq> Reactor { // * Exactly once if the compute cell's value changed as a result of the set_value call. // The value passed to the callback should be the final value of the compute cell after the // set_value call. - pub fn add_callback () + 'a>(&mut self, id: CellID, callback: F) -> Result { + pub fn add_callback ()>(&mut self, id: CellID, callback: F) -> Result { unimplemented!() } From 6e7b392c2f27995bb318d1760a913872933c5558 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Sat, 3 Sep 2016 02:46:30 -0700 Subject: [PATCH 14/15] react: Add boolean values test --- exercises/react/tests/react.rs | 38 ++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/exercises/react/tests/react.rs b/exercises/react/tests/react.rs index d9baf5c57..d6691751d 100644 --- a/exercises/react/tests/react.rs +++ b/exercises/react/tests/react.rs @@ -204,3 +204,41 @@ fn callbacks_should_not_be_called_if_dependencies_change_but_output_value_doesnt } assert_eq!(values, Vec::new()); } + +#[test] +#[ignore] +fn test_adder_with_boolean_values() { + // This is a digital logic circuit called an adder: + // https://en.wikipedia.org/wiki/Adder_(electronics) + let mut reactor = Reactor::new(); + let a = reactor.create_input(false); + let b = reactor.create_input(false); + let carry_in = reactor.create_input(false); + + let a_xor_b = reactor.create_compute(&vec![a, b], |v| v[0] ^ v[1]).unwrap(); + let sum = reactor.create_compute(&vec![a_xor_b, carry_in], |v| v[0] ^ v[1]).unwrap(); + + let a_xor_b_and_cin = reactor.create_compute(&vec![a_xor_b, carry_in], |v| v[0] && v[1]).unwrap(); + let a_and_b = reactor.create_compute(&vec![a, b], |v| v[0] && v[1]).unwrap(); + let carry_out = reactor.create_compute(&vec![a_xor_b_and_cin, a_and_b], |v| v[0] || v[1]).unwrap(); + + let tests = vec![ + (false, false, false, false, false), + (false, false, true, false, true), + (false, true, false, false, true), + (false, true, true, true, false), + (true, false, false, false, true), + (true, false, true, true, false), + (true, true, false, true, false), + (true, true, true, true, true), + ]; + + for (aval, bval, cinval, expected_cout, expected_sum) in tests { + assert!(reactor.set_value(a, aval).is_ok()); + assert!(reactor.set_value(b, bval).is_ok()); + assert!(reactor.set_value(carry_in, cinval).is_ok()); + + assert_eq!(reactor.value(sum).unwrap(), expected_sum); + assert_eq!(reactor.value(carry_out).unwrap(), expected_cout); + } +} From 2602332e4d787dbe0c60eddf48a25c90ab82fce2 Mon Sep 17 00:00:00 2001 From: Peter Tseng Date: Wed, 7 Sep 2016 23:53:30 -0700 Subject: [PATCH 15/15] react: it's an error to remove a nonexistent callback --- exercises/react/src/lib.rs | 6 ++---- exercises/react/tests/react.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/exercises/react/src/lib.rs b/exercises/react/src/lib.rs index 757c821f2..4ec956768 100644 --- a/exercises/react/src/lib.rs +++ b/exercises/react/src/lib.rs @@ -79,10 +79,8 @@ impl Reactor { // Removes the specified callback, using an ID returned from add_callback. // - // Return an Err (and you can change the error type) if the cell does not exist. - // - // If the callback does not exist (or has already been removed), it is up to you whether to - // return Ok(()) or an error. This should not interfere with other callbacks. + // Return an Err (and you can change the error type) if either the cell or callback + // does not exist. // // A removed callback should no longer be called. pub fn remove_callback(&mut self, cell: CellID, callback: CallbackID) -> Result<(), ()> { diff --git a/exercises/react/tests/react.rs b/exercises/react/tests/react.rs index d6691751d..6ac600811 100644 --- a/exercises/react/tests/react.rs +++ b/exercises/react/tests/react.rs @@ -162,7 +162,7 @@ fn removing_a_callback_multiple_times_doesnt_interfere_with_other_callbacks() { // We want the first remove to be Ok, but we don't care about the others. assert!(reactor.remove_callback(output, callback).is_ok()); for _ in 1..5 { - let _ = reactor.remove_callback(output, callback); + assert!(reactor.remove_callback(output, callback).is_err()); } assert!(reactor.set_value(input, 2).is_ok()); }