Created
January 18, 2019 04:55
-
-
Save kkspeed/a80cbb5336511ffd1992b0831c3c4681 to your computer and use it in GitHub Desktop.
A naive Rust Functional Reactive Programming Framework
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
use std::cell::RefCell; | |
use std::rc::Rc; | |
struct Core<T> { | |
observers: Vec<Box<Fn()>>, | |
value: Option<T>, | |
} | |
impl<T> Core<T> | |
where | |
T: Clone, | |
{ | |
fn new() -> Self { | |
Core::<T> { | |
observers: vec![], | |
value: None, | |
} | |
} | |
fn add_observer(&mut self, f: Box<Fn()>) { | |
self.observers.push(f); | |
} | |
fn set_value(&mut self, v: T) { | |
self.value.replace(v); | |
} | |
fn has_value(&self) -> bool { | |
self.value.is_some() | |
} | |
fn value(&self) -> T { | |
self.value.clone().unwrap() | |
} | |
fn drop_value(&mut self) { | |
self.value.take(); | |
} | |
fn notify(&self) { | |
if !self.has_value() { | |
return; | |
} | |
for f in self.observers.iter() { | |
f(); | |
} | |
} | |
} | |
pub struct Signal<T> { | |
core: Rc<RefCell<Core<T>>>, | |
} | |
impl<T> Signal<T> | |
where | |
T: Sized + Clone + 'static, | |
{ | |
pub fn new() -> Self { | |
Signal::<T> { | |
core: Rc::new(RefCell::new(Core::<T>::new())), | |
} | |
} | |
pub fn activate(&self, v: T) { | |
self.core.borrow_mut().set_value(v); | |
self.core.borrow().notify(); | |
self.core.borrow_mut().drop_value(); | |
} | |
pub fn sink(&self, f: Box<Fn(T)>) { | |
let this_core = self.core.clone(); | |
self.core.borrow_mut().add_observer(Box::new(move || { | |
f(this_core.borrow().value()); | |
})); | |
} | |
pub fn fmap<B: Sized + Clone + 'static>(&self, f: Box<Fn(T) -> B>) -> Signal<B> { | |
let new_signal = Signal::<B>::new(); | |
let this_core = self.core.clone(); | |
let new_core = new_signal.core.clone(); | |
self.core.borrow_mut().add_observer(Box::new(move || { | |
new_core | |
.borrow_mut() | |
.set_value(f(this_core.borrow().value())); | |
new_core.borrow().notify(); | |
new_core.borrow_mut().drop_value(); | |
})); | |
new_signal | |
} | |
pub fn filter(&self, f: Box<Fn(T) -> bool>) -> Signal<T> { | |
let new_signal = Signal::<T>::new(); | |
let new_core = new_signal.core.clone(); | |
let this_core = self.core.clone(); | |
self.core.borrow_mut().add_observer(Box::new(move || { | |
if !f(this_core.borrow().value()) { | |
new_core.borrow_mut().drop_value(); | |
return; | |
} | |
new_core.borrow_mut().set_value(this_core.borrow().value()); | |
new_core.borrow().notify(); | |
new_core.borrow_mut().drop_value(); | |
})); | |
new_signal | |
} | |
pub fn fold<A: Sized + Clone + 'static>(&self, init: A, f: Box<Fn(A, T) -> A>) -> Signal<A> { | |
let state: Rc<RefCell<Option<A>>> = Rc::new(RefCell::new(None)); | |
let new_signal = Signal::<A>::new(); | |
let new_core = new_signal.core.clone(); | |
let this_core = self.core.clone(); | |
self.core.borrow_mut().add_observer(Box::new(move || { | |
let v = init.clone(); | |
if !state.borrow().is_some() { | |
state.borrow_mut().replace(v); | |
} | |
let next = f(state.borrow().clone().unwrap(), this_core.borrow().value()); | |
state.borrow_mut().replace(next.clone()); | |
new_core.borrow_mut().set_value(next); | |
new_core.borrow().notify(); | |
new_core.borrow_mut().drop_value(); | |
})); | |
new_signal | |
} | |
pub fn sync<B: Sized + Clone + 'static>(&self, other: &Signal<B>) -> Signal<(T, B)> { | |
let new_signal = Signal::<(T, B)>::new(); | |
let new_core = new_signal.core.clone(); | |
let this_core = self.core.clone(); | |
let state: Rc<RefCell<(Option<T>, Option<B>)>> = Rc::new(RefCell::new((None, None))); | |
let state_shared = state.clone(); | |
self.core.borrow_mut().add_observer(Box::new(move || { | |
let val = ( | |
Some(this_core.borrow().value()), | |
(*state_shared.borrow()).1.clone(), | |
); | |
*state_shared.borrow_mut() = val.clone(); | |
match val { | |
(Some(x), Some(y)) => { | |
new_core.borrow_mut().set_value((x, y)); | |
new_core.borrow().notify(); | |
new_core.borrow_mut().drop_value(); | |
*state_shared.borrow_mut() = (None, None); | |
} | |
_ => (), | |
}; | |
})); | |
let new_core = new_signal.core.clone(); | |
let other_core = other.core.clone(); | |
let state_shared = state.clone(); | |
other.core.borrow_mut().add_observer(Box::new(move || { | |
let val = ( | |
(*state_shared.borrow()).0.clone(), | |
Some(other_core.borrow().value()), | |
); | |
*state_shared.borrow_mut() = val.clone(); | |
match val { | |
(Some(x), Some(y)) => { | |
new_core.borrow_mut().set_value((x, y)); | |
new_core.borrow().notify(); | |
new_core.borrow_mut().drop_value(); | |
*state_shared.borrow_mut() = (None, None); | |
} | |
_ => (), | |
}; | |
})); | |
new_signal | |
} | |
pub fn merge(&self, other: &Signal<T>) -> Signal<T> { | |
let new_signal = Signal::<T>::new(); | |
let new_core = new_signal.core.clone(); | |
let this_core = self.core.clone(); | |
self.core.borrow_mut().add_observer(Box::new(move || { | |
new_core.borrow_mut().set_value(this_core.borrow().value()); | |
new_core.borrow().notify(); | |
new_core.borrow_mut().drop_value(); | |
})); | |
let new_core = new_signal.core.clone(); | |
let other_core = other.core.clone(); | |
other.core.borrow_mut().add_observer(Box::new(move || { | |
new_core.borrow_mut().set_value(other_core.borrow().value()); | |
new_core.borrow().notify(); | |
new_core.borrow_mut().drop_value(); | |
})); | |
new_signal | |
} | |
} | |
#[cfg(test)] | |
mod tests { | |
use super::*; | |
#[test] | |
fn test_fmap() { | |
let v = Rc::new(RefCell::new(0)); | |
let s = Signal::<i32>::new(); | |
let z = v.clone(); | |
let _ = s | |
.fmap(Box::new(|x| x * 2)) | |
.sink(Box::new(move |x| *z.borrow_mut() = x)); | |
s.activate(10); | |
assert_eq!(*v.borrow(), 20); | |
s.activate(20); | |
assert_eq!(*v.borrow(), 40); | |
} | |
#[test] | |
fn test_filter_fold() { | |
let v = Rc::new(RefCell::new(-1)); | |
let z = v.clone(); | |
let s = Signal::<i32>::new(); | |
let _ = s | |
.fmap(Box::new(|x| x * 2)) | |
.filter(Box::new(|x| x % 3 == 0)) | |
.fold(0, Box::new(|x, y| x + y)) | |
.sink(Box::new(move |x| *z.borrow_mut() = x)); | |
s.activate(0); | |
assert_eq!(*v.borrow(), 0); | |
s.activate(1); | |
assert_eq!(*v.borrow(), 0); | |
s.activate(2); | |
assert_eq!(*v.borrow(), 0); | |
s.activate(3); | |
assert_eq!(*v.borrow(), 6); | |
s.activate(4); | |
assert_eq!(*v.borrow(), 6); | |
s.activate(5); | |
assert_eq!(*v.borrow(), 6); | |
s.activate(6); | |
assert_eq!(*v.borrow(), 18); | |
} | |
#[test] | |
fn test_sync() { | |
let s1 = Signal::<i32>::new(); | |
let s2 = Signal::<i32>::new(); | |
let s3 = s1.sync(&s2); | |
let v: Rc<RefCell<Option<(i32, i32)>>> = Rc::new(RefCell::new(None)); | |
let z = v.clone(); | |
s3.sink(Box::new(move |x| *z.borrow_mut() = Some(x))); | |
s1.activate(3); | |
assert!(!v.borrow().is_some()); | |
*v.borrow_mut() = None; | |
s2.activate(5); | |
assert_eq!(*v.borrow(), Some((3, 5))); | |
*v.borrow_mut() = None; | |
s2.activate(6); | |
assert!(!v.borrow().is_some()); | |
} | |
#[test] | |
fn test_merge() { | |
let s1 = Signal::<i32>::new(); | |
let s2 = Signal::<i32>::new(); | |
let s3 = s1.merge(&s2); | |
let v = Rc::new(RefCell::new(0)); | |
let z = v.clone(); | |
s3.sink(Box::new(move |x| *z.borrow_mut() = x)); | |
s1.activate(3); | |
assert_eq!(*v.borrow(), 3); | |
s2.activate(5); | |
assert_eq!(*v.borrow(), 5); | |
s1.activate(6); | |
assert_eq!(*v.borrow(), 6); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment