Atoms as abstractions over Promises and Streams


#1

Atoms is abstration over Promises and Streams.

Atoms support:

  1. automatic dependency tracking
  2. automatic lifecycle management

Every atom known:

  1. His value or error
  2. Set of master atoms
  3. Set of slave atoms

Atom destroys when losing last slave atom. If master change state - slaves invalidates. Atoms need not GC.

Proposal API:

interface Atom<T> {

    constructor( config : {

        // callback that uses to pull value by formula (called from get() or update())
        pull : ( prev : T ) => T

        // callback that uses to put value to another state (called from set())
        put : ( next : T , prev : T )=> T

        // callback that called when atom want to destroy
        reap : ()=> void

    } )

    /// get stored value, or pull value and store; trows error if stored error
    get : ()=> T

    /// push new value to atom
    push : ( next : T )=> T

    /// try to set value (controlled by put())
    set : ( next : T )=> T

    /// save error
    fail : ( error : Error )=> this

    /// invalidate and deferred update if atom has slaves
    update : ()=> void

    /// Thenable API
    then<P> : ( done : ( next : T )=> P , fail : Error => P  )=> Promise<P>
    catch<P> : ( fail : Error => P  )=> Promise<P>

    /// Event Emitter API; call handler deffered after atom state changed
    on : ( handle : ( next : T , prev : T )=> void )=> this
}


interface AtomStatic {

    /// constructor
    new<T>() : Atom<T>
    
    /// force to cascade atoms synchronizing
    sync() : void

    /// atoms that have not slaves, but automatic evaluated
    targets : Set<Atom<any>>
}

Usage:

var a = new Atom({ pull : prev => 1 }) // define a = 1
var b = new Atom({ pull : prev => a.get() + 1 }) // define a = b + 1
var c = new Atom({ pull : prev => a.get() + b.get() }) define c = a + b

Atom.targets.add( c ) // start auto evaluating c. c=3 now.
a.push( 2 ) // a = 2: b planned to defer update

Atom.sync() // cascade updating atoms: b = 3; c = 5
alert( c.get() ) // 5

var config = new Atom({ pull : prev => {
    setTimeout( ()=> config.push({ name : 'Jin' }) , 1000 )
} })

var greeting = new Atom({ pull : prev => 'Hello, ' + config.get().name }) // waits until config defined

var alerter = new Atom( prev => alert( greeting.get() ) )
Atom.targtes.add( aleter )  // alerts "Hello, Jin" after 1 second

You can see some prototype here: https://github.com/nin-jin/pms-jin/tree/master/atom/prop

More examples here (russian): https://habrahabr.ru/post/240773/

Some theory here (russian): https://habrahabr.ru/post/235121/

Let us discuss about detailed api.


#2

This post may be more appropriate for ES Discuss.