Last active
August 29, 2021 11:39
-
-
Save KEIII/52f35c39ce1833ee392b7705bd676b3e to your computer and use it in GitHub Desktop.
Atom
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
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