Skip to content

Instantly share code, notes, and snippets.

@KEIII
Last active August 29, 2021 11:39
Show Gist options
  • Save KEIII/52f35c39ce1833ee392b7705bd676b3e to your computer and use it in GitHub Desktop.
Save KEIII/52f35c39ce1833ee392b7705bd676b3e to your computer and use it in GitHub Desktop.
Atom
const observers = [];
const call = f => f();
const callAll = x => () => x.forEach(call);
let t;
const notify = () => {
clearTimeout(t);
t = setTimeout(callAll(observers), 0);
};
// The autorun function accepts one function that should run every time anything it observes changes.
// It also runs once when you create the autorun itself.
// Returns a disposer function that you need to call in order to cancel it.
const autorun = observer => {
observers.push(observer);
observer();
return () => {
const index = observer.indexOf(observer);
if (index !== -1) observers.splice(index, 1);
};
};
const STATUS = {
OUTDATED: 1,
COMPUTING: 2,
ACTUAL: 3,
};
const atom = (depends, fn) => {
const connect = () => {
// сообщаем атомам, от которых мы зависим,
// что мы терперь от них зависим
self.depends.forEach(atom => atom.subscribe(self));
};
const self = {
depends, // список зависимостей
fn, // функция для расчёта значения
subscribers: [], // кто зависит от нас
cache: undefined, // вычисленное значение будет закешировано тут
status: STATUS.OUTDATED, // актуальность вычисленного значения
get: () => {
if (self.status === STATUS.COMPUTING) {
throw new Error('Logic error: circle dependencies');
}
if (self.status !== STATUS.ACTUAL) {
self.status = STATUS.COMPUTING;
self.cache = self.fn(...self.depends.map(atom => atom.get()));
self.status = STATUS.ACTUAL;
}
return self.cache;
},
set: (depends, fn) => {
// удалям себя из списка зависимостей у тех, от кого мы зависим сейчас
self.depends.forEach(atom => atom.disconnect(self));
self.depends = depends; // заменяем список зависимостей
self.fn = fn; // и функцию для расчёта значения
// помечаем значениее как неактуальное,
// актуальное значение будет вычисленно позже по запросу
self.outdated();
connect(); // сообщаем новым зависимостям, что мы терперь от них зависим
notify(); // сообщаем внешним подписчикам, что есть изменения
},
next: x => {
self.set(self.depends, () => x);
},
outdated: () => {
self.status = STATUS.OUTDATED; // помечаем текущее значение как неактуальное
// сообщаем всем, кто зависит от нас, что у них значение теперь тоже неактуальное
self.subscribers.forEach(atom => atom.outdated());
},
subscribe: atom => self.subscribers.push(atom), // какой-то атом говорит, что зависит от нас
disconnect: atom => {
// какой-то атом говорит, что больше не зависит от нас
const index = self.subscribers.indexOf(atom);
if (index !== -1) self.subscribers.splice(index, 1);
},
};
connect();
return self;
};
// Lifts a function that works on normal values to work on atoms.
const lift = f => {
return (...depends) => atom(depends, f);
};
// of :: a -> m a
const of = a => atom([], () => a);
// fmap :: (a -> b) -> m a -> m b
const fmap = f => ma => atom([ma], f);
// chain :: (a -> m b) -> m a -> m b
const chain = f => fmap(a => f(a).get());
const a = of(1);
const b = of(2);
const c = atom([a, b], (a, b) => {
console.log('a + b');
return a + b;
});
const d = atom([a, c], (a, c) => a * c);
autorun(() => {
console.log(`${a.get()} + ${b.get()} = ${c.get()}; D = ${d.get()}`);
});
a.next(3);
a.next(4);
a.next(5);
console.log('lift: ', lift((a, b) => a / b)(d, a).get());
const x1 = fmap(x => [x, x])(d);
console.log('fmap_1: ', x1.get());
d.next(3);
console.log('fmap_2: ', x1.get());
const x2 = chain(x => of(x + 1))(d);
console.log('chain_1: ', x2.get());
d.next(42);
console.log('chain_2: ', x2.get());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment