Live data from Hacker News

Managing State with Signals

tonsky.me

101–110 of 126 posts

Re: Managing State with Signals

#101

Earlier quoted context omitted.

> Mathematically, RxJS and Vue/Solid Qwik like FRP are equivalent. Yes, these are basically the same thing. However, they have little to do with Conal Elliott's "FRP". Which itself is also badly named. > Meijer (who invented Rx) Well "invented". What's a bit surprising is that with both Rx and the Rx-style "FRP", there either is no public history of the ideas at all (Rx) or it is patently wrong (Rx-style "FRP"). Or a…

I think the important contribution of Meijer and RX is realizing the duality of IEnumerable and IObservable which enabled them to use the same programming constructs. The meat of RX is in all the combinators so you can express complex behaviours very succinctly.

Hmm...Smalltalk had the same iteration methods over streams and collections since at least Smalltalk-80, so not sure what the important contribution here is.

(Waiting for the LISPers to chime in...)

Re: Managing State with Signals

#102
post #97

This article would be more compelling if the demo videos showed a UI that actually worked. I'm left wondering if their day job is building consent dialogs for websites.

What do you mean by “actually worked”?

The checkmark never disappears in the checkboxes however the standard behavior for a checkbox is that the checkmark should disappear (or appear) when the checkbox is toggled.

Furthermore, towards the end of one of the videos, the checkmark seems to turn from green to gray and back to green again with a single click.

Re: Managing State with Signals

#103

Not sure I like the mixed push/pull approach. If you're already traversing the tree to mark nodes as possibly dirty, you might as well recompute the node's value and store it while you're there. Otherwise on pull/lazy update, you're traversing the tree all over again! Terrible for cache locality, particularly for large graphs. You might be tempted to say that the lazy approach might avoid some recomputations, but if…

In a push-based approach, can you bundle state changes into a transaction and recompute derived state when the transaction ends and all inputs have finished changing?

Re: Managing State with Signals

#104

It's interesting how every build system, frontend framework, programming language implements its own promise pipeline/delayed execution/observables/event propagation. But the implementations are rarely extracted out for general purpose usage and rarely have a rich API. I've been thinking a lot about a general purpose "epoll" which be registered on objects that change. I want to be able to register a reaction to a seq…

I believe the consequence of this line of thinking is the old idea of writing domain-specific interpreters. In your example, the UI generate a list of commands, and you execute/reduce over at an arbitrary time to get to a new global state, instead of tangling all the UI code with dispatching commands and managing state immediately.

Re: Managing State with Signals

#105

Not sure I like the mixed push/pull approach. If you're already traversing the tree to mark nodes as possibly dirty, you might as well recompute the node's value and store it while you're there. Otherwise on pull/lazy update, you're traversing the tree all over again! Terrible for cache locality, particularly for large graphs. You might be tempted to say that the lazy approach might avoid some recomputations, but if…

I spent 11+ years at Goldman, working in SecDb Core, and various SecDb-related infrastructure.

Goldman's Slang language has subsets of both lazily-evaluated backward-propagating dataflow graph ("The SecDb Graph") and forward-propagating strict-evaluating dataflow graph ("TSecDb"). They both have their use cases. The lazily evaluated graph is much more efficient in cases where you have DAG nodes close to the output that are only conditionally dependent upon large sub-graphs, especially in cases where you might be skipping some inputs, and so the next needed graph structure might not be known at invalidation time.

Ideally, you'd have some compile-time/load-time static strictness analysis to determine which nodes are always needed (similar to what GHC does to avoid a lot of needless thunk creation) along with some dynamic GC-like strictness analysis that works backward from output nodes to figure out which of the potentially-lazy nodes should be strictly evaluated. In the general case, the graph dependencies may depend upon the particular dynamic values of some graph nodes (the nodes whose values affected the graph structure used to be called "purple children" in SecDb, but that lead to Physics/Statistics PhDs coming to the core team confused by exceptions like "Purple children should not exist in subgraph being compiled to serializable lambda")

TSecDb already contains a similar analysis to prune dead code nodes from the dataflow DAG after the DAG structure is dynamically updated. (For instance, when a new stock order comes in, a big chunk of TSecDb subgraph is created to handle that one order, and the TSecDb garbage collector immediately runs and removes all of the graph nodes that can't possibly affect trading decisions for that order. This also means that developers new to TSecDb often get their logging code automatically pruned from the graph because they've forgotten to mark it as a GC root (TsDevNull(X))... and it's pretty bad logging code if it affects the trading decisions.)

Risk exposure calculations (basically calculating the partial derivatives of the value of everything on the books with respect to most of the inputs) are done mostly on the lazy graph, and real-time trading decisions are done mostly on the strict graph.

Re: Managing State with Signals

#107

Earlier quoted context omitted.

Have you tried the dark mode? I thought my browser was broken when I turned it on.

Wow, truly night mode. Hilarious!

It's one of the first times I've been delighted by an unexpected UI in years. We've all settled on conventions in a world where anything is possible.

Re: Managing State with Signals

#108

It's interesting how every build system, frontend framework, programming language implements its own promise pipeline/delayed execution/observables/event propagation. But the implementations are rarely extracted out for general purpose usage and rarely have a rich API. I've been thinking a lot about a general purpose "epoll" which be registered on objects that change. I want to be able to register a reaction to a seq…

I'm not sure generalisation here is ideal - quite often these are specialised implementations for specialised cases, and using them in other situations tends to only work so far.

For example, I'm currently working on a spreadsheet-style tool where the reactivity is largely being handled by SolidJS signals. It works fairly well up to a point (and that point is probably good enough for the client's needs), but it's very clear that there are big limitations here, and a more complete solution would bundle its own reactivity system. Things like computing results that spill across several cells just don't map cleanly onto conventional signals, so we instead have lots of ways to manually trigger recomputation, rather than just setting up the perfect pipeline flow. Likewise, figuring out where data loops are happening just isn't really possible.

That's not to say that SolidJS is bad for this sort of stuff - it has been great, and it's impressive how well the underlying reactive primitives work even for this project. But I think even when the underlying theory between these tools is pretty similar, the practical tradeoffs that need to be made are very different. And as a result, the different libraries servicing these different use cases will look very different.

I suspect this is the reason why these implementations are rarely extracted out more broadly. The sort of system that works well for one situation will rarely work so well for another.

Re: Managing State with Signals

#110

Earlier quoted context omitted.

For GP’s stream-transformer / state-machine equivalence to work, you need to sprinkle option types throughout so each input yields some kind of output, even if empty. So more like def co(): i = yield None # hurray for off-by-one streams j = yield None while True: i = yield i + j j = yield None This won’t help if the output stream produces more than one output item from each input item. You could sprinkle lists instea…

But I am not criticizing his stream-transformer / state-machine equivalence, I am just curious why he thinks functions of Stream A -> Stream B have to produce exactly 1 output for exactly 1 input. Now, I know that Haskell in its pre-monad days used to have main have signature [Response] -> [Request]: the lists being lazy, they're essentially streams. Each Request produced by the main would result in a Response being…

I guess what I wanted to say was that to me (given that the comment was presumably targeted at people who already know how all of FRP, stream transformers, and state transducers work, or can at least make a good guess) it was within the limits of acceptable sloppiness to mix up (State, Input) -> (State, Output), (State, Input) -> (State, Maybe Output), and (State, Input) -> (State, [Output]), or the equivalent on the stream transformation side. The point of the comment does not really depend on what you choose here.
Post reply on HN