Created
February 6, 2020 16:30
-
-
Save freddi301/90f66617aebefb41f57667fea65ec11c to your computer and use it in GitHub Desktop.
Rust hooks
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::assert; | |
use std::rc::Rc; | |
mod rust_hooks { | |
use std::cell::{Cell, RefCell}; | |
use std::rc::Rc; | |
pub trait Notifiable { | |
fn notify(&self); | |
} | |
pub trait Subscribeable { | |
fn subscribe(&self, listener: Rc<dyn Notifiable>); | |
} | |
pub trait Valuable<Value> { | |
fn value(&self) -> Option<Value>; | |
} | |
#[macro_export] | |
macro_rules! use_state { | |
() => {{ | |
use rust_hooks::*; | |
Rc::new(State::new()) | |
}}; | |
} | |
#[macro_export] | |
macro_rules! use_memo { | |
($body:expr, [$($dependency:ident),*]) => { | |
{ | |
use rust_hooks::*; | |
let wired = Rc::new(Memo::new({ | |
$(let $dependency = $dependency.clone();)* | |
move || { | |
if let ($(Some($dependency),)*) = ($($dependency.value(),)*) { | |
Some(dbg!($body)) | |
} else { | |
None | |
} | |
} | |
})); | |
$($dependency.subscribe(wired.clone());)* | |
wired | |
} | |
}; | |
} | |
#[macro_export] | |
macro_rules! use_effect { | |
($body:expr, [$($dependency:ident),*]) => { | |
{ | |
use rust_hooks::*; | |
let wired = Rc::new(Effect::new({ | |
$(let $dependency = $dependency.clone();)* | |
move || { | |
if let ($(Some($dependency),)*) = ($(($dependency).value(),)*) { | |
Some(dbg!($body)) | |
} else { | |
None | |
} | |
} | |
})); | |
$($dependency.subscribe(wired.clone());)* | |
wired | |
} | |
}; | |
} | |
#[macro_export] | |
macro_rules! use_scanner { | |
(|$state:pat| $body:expr, $initial:expr, [$($dependency:ident),*]) => { | |
{ | |
use rust_hooks::*; | |
let wired = Rc::new(Scanner::new({ | |
$(let $dependency = $dependency.clone();)* | |
move |$state| { | |
if let ($(Some($dependency),)*) = ($(($dependency).value(),)*) { | |
Some(dbg!($body)) | |
} else { | |
None | |
} | |
} | |
}, $initial)); | |
$($dependency.subscribe(wired.clone());)* | |
wired | |
} | |
}; | |
} | |
struct Notifier { | |
listeners: Vec<Rc<dyn Notifiable>>, | |
} | |
impl Notifier { | |
fn new() -> Self { | |
Notifier { | |
listeners: Vec::new(), | |
} | |
} | |
fn subscribe(&mut self, listener: Rc<dyn Notifiable>) { | |
self.listeners.push(listener); | |
} | |
} | |
impl Notifiable for Notifier { | |
fn notify(&self) { | |
for listener in &self.listeners { | |
listener.notify(); | |
} | |
} | |
} | |
pub struct State<Value> { | |
value: Cell<Option<Value>>, | |
notifier: RefCell<Notifier>, | |
} | |
impl<Value> State<Value> { | |
pub fn new() -> Self { | |
State { | |
value: Cell::new(None), | |
notifier: RefCell::new(Notifier::new()), | |
} | |
} | |
pub fn set(&self, value: Value) { | |
self.value.set(Some(value)); | |
self.notifier.borrow().notify(); | |
} | |
} | |
impl<Value> Subscribeable for State<Value> { | |
fn subscribe(&self, listener: Rc<dyn Notifiable>) { | |
self.notifier.borrow_mut().subscribe(listener); | |
} | |
} | |
impl<Value: Copy> Valuable<Value> for State<Value> { | |
fn value(&self) -> Option<Value> { | |
self.value.get() | |
} | |
} | |
pub struct Memo<F: Fn() -> Option<Value>, Value> { | |
closure: F, | |
value: Cell<Option<Value>>, | |
notifier: RefCell<Notifier>, | |
} | |
impl<F: Fn() -> Option<Value>, Value> Memo<F, Value> { | |
pub fn new(closure: F) -> Self { | |
Memo { | |
closure, | |
value: Cell::new(None), | |
notifier: RefCell::new(Notifier::new()), | |
} | |
} | |
} | |
impl<F: Fn() -> Option<Value>, Value> Notifiable for Memo<F, Value> { | |
fn notify(&self) { | |
let closure = &self.closure; | |
let value = closure(); | |
self.value.set(value); | |
self.notifier.borrow().notify(); | |
} | |
} | |
impl<F: Fn() -> Option<Value>, Value> Subscribeable for Memo<F, Value> { | |
fn subscribe(&self, listener: Rc<dyn Notifiable>) { | |
self.notifier.borrow_mut().subscribe(listener); | |
} | |
} | |
impl<F: Fn() -> Option<Value>, Value: Copy> Valuable<Value> for Memo<F, Value> { | |
fn value(&self) -> Option<Value> { | |
self.value.get() | |
} | |
} | |
pub struct Effect<F: Fn() -> Option<Value>, Value> { | |
closure: F, | |
value: Cell<Option<Value>>, | |
notifier: RefCell<Notifier>, | |
} | |
impl<F: Fn() -> Option<Value>, Value> Effect<F, Value> { | |
pub fn new(closure: F) -> Self { | |
Effect { | |
closure, | |
value: Cell::new(None), | |
notifier: RefCell::new(Notifier::new()), | |
} | |
} | |
} | |
impl<F: Fn() -> Option<Value>, Value> Notifiable for Effect<F, Value> { | |
fn notify(&self) { | |
let closure = &self.closure; | |
let value = closure(); | |
self.value.set(value); | |
self.notifier.borrow().notify(); | |
} | |
} | |
impl<F: Fn() -> Option<Value>, Value> Subscribeable for Effect<F, Value> { | |
fn subscribe(&self, listener: Rc<dyn Notifiable>) { | |
self.notifier.borrow_mut().subscribe(listener); | |
} | |
} | |
impl<F: Fn() -> Option<Value>, Value: Copy> Valuable<Value> for Effect<F, Value> { | |
fn value(&self) -> Option<Value> { | |
self.value.get() | |
} | |
} | |
pub struct Scanner<F: Fn(Value) -> Option<Value>, Value> { | |
closure: F, | |
value: Cell<Value>, | |
notifier: RefCell<Notifier>, | |
} | |
impl<F: Fn(Value) -> Option<Value>, Value> Scanner<F, Value> { | |
pub fn new(closure: F, initial: Value) -> Self { | |
Scanner { | |
closure, | |
value: Cell::new(initial), | |
notifier: RefCell::new(Notifier::new()), | |
} | |
} | |
} | |
impl<F: Fn(Value) -> Option<Value>, Value: Copy> Notifiable for Scanner<F, Value> { | |
fn notify(&self) { | |
let closure = &self.closure; | |
let value = closure(self.value.get()); | |
if let Some(value) = value { | |
self.value.set(value); | |
self.notifier.borrow().notify(); | |
} | |
} | |
} | |
impl<F: Fn(Value) -> Option<Value>, Value> Subscribeable for Scanner<F, Value> { | |
fn subscribe(&self, listener: Rc<dyn Notifiable>) { | |
self.notifier.borrow_mut().subscribe(listener); | |
} | |
} | |
impl<F: Fn(Value) -> Option<Value>, Value: Copy> Valuable<Value> for Scanner<F, Value> { | |
fn value(&self) -> Option<Value> { | |
Some(self.value.get()) | |
} | |
} | |
} | |
fn main() { | |
use rust_hooks::Valuable; | |
#[derive(Debug, Clone, Copy)] | |
struct Dumb<T>(T); | |
let a = use_state!(); | |
let b = use_memo!(a + 1, [a]); | |
let c = use_memo!(a * b, [a, b]); | |
use_effect!(dbg!(a), [a]); | |
use_effect!(dbg!(b), [b]); | |
use_effect!(dbg!(c), [c]); | |
let d = use_scanner!(|d| d + a, 0, [a]); | |
let e = use_scanner!(|Dumb(e)| Dumb(e + d), Dumb(0), [d]); | |
use_effect!(dbg!(d), [d]); | |
use_effect!(dbg!(e), [e]); | |
// use_effect!((a, b, c, d), [a, b, c, d]); | |
a.set(1); | |
assert!(b.value() == Some(2)); | |
a.set(100); | |
} | |
/* | |
TODO | |
- useMemo should be lazy (do nothing if no subscribers) | |
- useEffect should be strict (always execute closure) | |
- useScanner should be lazy | |
- fix notifier notifying multiple times (remove dropped listeners) | |
*/ | |
mod lazy { | |
use std::cell::UnsafeCell; | |
use std::mem::transmute; | |
use std::ops::Deref; | |
// TODO FnMut -> FnOnce | |
enum Status<Thunk: FnMut() -> Value, Value> { | |
Value(Value), | |
Thunk(Thunk), | |
} | |
pub struct Lazy<Thunk: FnMut() -> Value, Value> { | |
inner: UnsafeCell<Status<Thunk, Value>>, | |
} | |
impl<Thunk: FnMut() -> Value, Value> Lazy<Thunk, Value> { | |
pub fn new(producer: Thunk) -> Self { | |
Lazy { | |
inner: UnsafeCell::new(Status::Thunk(producer)), | |
} | |
} | |
} | |
impl<Thunk: FnMut() -> Value, Value> Deref for Lazy<Thunk, Value> { | |
type Target = Value; | |
fn deref(&self) -> &Self::Target { | |
let status: &mut Status<Thunk, Value> = unsafe { transmute(self.inner.get()) }; | |
match status { | |
Status::Value(value) => value, | |
Status::Thunk(producer) => { | |
let value = producer(); | |
*status = Status::Value(value); | |
self.deref() | |
} | |
} | |
} | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment