dy / spect

Observable selectors in DOM
https://dy.github.io/spect
MIT License
76 stars 8 forks source link

v → f, v. Shaving off API, v as proxy #226

Closed dy closed 4 years ago

dy commented 4 years ago

Now v is subscribe, calc and transform. Can be confusing sometimes. Splitting it to f for fx and v for value makes code cleaner and internal aspects code flow more pleasant, along with el.props for element properties. That pivots from $,h,v...

let a = v(0), b = v(1), c = v(2)
fx([a, b, c, el.props], ([a,  b, c, el.props]) => effect)

// that would cause useless v for now, just to react to changes in deps:
v([a,b,c,el.props])([a,b,c,el.props] => effect)
// v is mispurposed, not used as value observable

The purpose of f is subscribing to changes in a, can be useful for organizing rendering/updating loops triggered by deps.

Also - likely re-rendering full tree in fx is not expensive, since it's cached.

That removes need in mapping observables, and essentially makes optional for h to take in observables.

dy commented 4 years ago

What's the elegant way, instead of wrapping rendering into loop and wrapping field into observable? Ideally it is easily upgradable from simple field (static → dynamic). a + map is pretty good tbh:

fx(el.props, props => h`<${el}>${ props.data.map(item => h`<a>${ item.title }</a>`) }</>`)
// a + map
h`<${el}>${ a(el, 'data').map(data => data.map(item => h`<a>${item.title}</a>`) }</>`
// f
h`<${el}>${ f(a(el, 'data'), data => data.map(item => h`<a>${item.title}</a>`)) }</>`
// a as v
h`<${el}>${ a(el, 'data', data => data.map(item => h`<a>${item.title}</a>`)) }</>`

// map operator
h`<${el}>${ map(data => data.map(item => h`<a>${item.title}</a>`))(a(el, 'data')) }</>`
dy commented 4 years ago

Map operator alleviates situation with stuffing everything into v. In fact, v can be moved to state in strui, along with map, from, fx, calc, input, attr and others. h can be moved out too - xhtm? So $ leaves only as aspect connector, basically spect@13, super-performant. Alternatively, selector collections, if make sense.

dy commented 4 years ago

The question is about h and v`a${b}c` - they seem to be out of strui scope. v is not 100% stream or subject, it is value container, streamable though. Moving any-case observer stuff from v to strui/from, and keeping v thin (sube-covering only) features it's value container nature, emphasizing spect uniqueness.

dy commented 4 years ago

A downside of turning down groups from v:

Is that possible to resurrect Proxy-based v function wrapper, by default acting as an object, but callable? It could be just transparent observable layer, exposing all internal prototype props, so that the only drawback would be tainted direct primitive values #64, #160, #182. That is possible to make it be transparent for object/array data.

Transparent observable any-value wrapper can distinguish spect/v from regular observable tech, which follows the element-props practice (nice & short). Not even sure if it needs to be a function, mb for get/set:

let x = v(1), y = v(2)
h`sum: ${ x + y }` //static
h`sum: ${ map((x, y) => )(x, y) }` //dynamic + strui
h`sum: ${ x[Symbol.observable]().map(x => x + y) } //dynamic symbol
h`sum: ${ x.map(x => x + y) } //dynamic self - it is observable too!
h`sum: ${ v(x, y).map((x, y) => x + y) }` //dynamic group observable (no obj/array yay)
Sketch proxy proposal, declined Regarding the name conflict with existing structures: ```js // name clash with array let data = v([]) // map method here could be directed to [].map, but called anew for each data piece h`items: ${ data.map(item => h`item ${item.id}` ) }` // COULD BE equivalent to h`items: ${ v.from(data).map(items => items.map(item => h`item ${item.id}`)) }` // but that's internally super-complex and externally implicit magic // better give hints to various planes h`items: ${ data.map( items => items.map(item => h`item: ${ item.id }` ) ) }` // or h`items: ${ data |> map(items => items.map(item => h`item: ${ item.id }`)) }` data.push(item1, item2, item3) // to call simple observable map, switch to observable level: data[Symbol.observable]().map(items => items) // method forwarding with date let date = v(new Date) // static render h`time: ${ date.toISOString() }` // dynamic render h`time: ${ date.map(date => date.toISOString()) }` ```

Get/set: let x = v(init)x(reinit)useState(init)setState(reinit), this way function is the only special clause of v:

let x = v(1), y = v(2), sum = v(x, y).map((x, y) => x + y)
// or (x, y) |> map((x, y) => x + y) with n-ary pipelines + strui
x(2), +sum // 4

// fn observable is the only exception - like dynamic method
let fn = v(() => fn)
fn(...args) // direct call
fn(() => fn2) // reinit

Tbh mixing in value props into observable looks a bit messy, esp. in case of map. It is useful to have observable storage, yes, but it feels like observable level should be in a different plane, behind Symbol.observable, that makes v not real observable instance, but observable-compatible (not necessarily bad). Also that raises question of mapping:

v(a, b, c) // no map possible - what's use from this group?
abc = v([a, b, c]) // not real group, but array value, items are not observed
abc[0] // first item, not observable
abc = v({a, b, c}) // not group, but dict. Each value is not observed
abc.a // a item, not observable
// ... too many downsides

// would be useful to have, for example, group:
abc = v(a, b, c) // observable of multiple items
abc.map((a,b,c) => a + b + c) // simple observable result
abc() // get value - what?? multiple returns are not possible in js, only webassembly
abc(a1, b1, c1) // set new value

So considering that ↑ confusion, v better be regular observable channel, as so:

v(el.props).map(({select}) => select ? select.map(...) : default)

Exposing internal props doesn't have much use and rather perplex things.

dy commented 4 years ago

So there are essentially 3 separate concerns: calc/group, value container (channel), object/array observable proxy.

dy commented 4 years ago

Not sure about selector-collection - little implementational difference from only-aspects, they come at very low price. The rest is implemented in v22.