RadicalZephyr / sodium-rust

FRP implementation in Rust
BSD 3-Clause "New" or "Revised" License
1 stars 0 forks source link

Support strong typing guarantees performantly #18

Open RadicalZephyr opened 7 months ago

RadicalZephyr commented 7 months ago

In my currently quite limited experience with Sodium, I've noticed that Streams with the same types tend to abound, though each one will actually have different semantics. For instance, in my TicTacToe implementation, the parsing and validation process for the input index to play at. I have several Stream<usize> representing variously:

However, another case of carrying the same value through, but with different semantic meaning is for when you have a Stream<()>, i.e. a Stream that passes no data, just a signal. In this case, changing the type from one ZST to another introduces a new Node to the Sodium graph, degrading the complexity making this no-op change carry a potentially significant performance penalty.

It would be ideal for adding a simple map from one ZST to another to be a very low-cost or essentially free operation.

RadicalZephyr commented 7 months ago

I still believe it may be possible to make simple computation a much lower cost proposition using a similar design to the Iterator and Future traits, of composition of generic types that can get monomorphized and inlined into much more optimized generated code.

I think that this will probably end up looking something like the trait structures that I have played with in my own implementations, somehow fused with the current Sodium design to allow sharing nodes when that's desirable.

RadicalZephyr commented 7 months ago

Ultimately tho, from what I've seen of implementing Tic Tac Toe in Sodium, sharing nodes is the rule not the exception. Long chains of combinators without sharing intermediate results are not very common.

In fact, in my current design described above, the only reason that those three separate streams exist is to allow separate error checking, but I've now unified those three separate error streams into one Error type. This means that I could actually implement that as one map to do all of the parsing and validation that return a Result<usize, Error>, compact all the processing into one Node and then split that Stream<Result<usize, Error>> into (Stream<usize>, Stream<Error>).

It's probably possible to do this in most cases where it would be natural to have a sequence of combinators.