-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Let compiler infer types and ports in fg.connect() #66
Comments
Hi! Thanks for bringing this up. I completely agree that it would be nice to have a more type-safe API, in particular for such central, user-facing functions like
While you can implement blocks with struct fields that return typed output ports, how could the flowgraph learn about them? You have to have some kind of function To overcome the previous issue, ports have to be in some sort of vector. This allows the flowgraph to access them through, for example, It goes on and on and on like this. While things may seem trivial, implementing a typed API is not straightforward. I think there are good answers to all these issues, but one just has to give it a try. If you come up with something, it will be very much appreciated. I'm happy to help as good as I can. It's also on my list, but I just didn't have the time yet :-/ |
Could we make just all connections have some sort of "ID" similar to the aforementioned One example, which I am not sure about whether it would really work: |
Not so simple neither because some blocks may be template based (not only type, but number of inputs/outputs) and thus it would require extra works with macro at compile-time. Also concerning the syntax, an alternative: I have started to experiment to provide a simpler syntax: https://github.com/loic-fejoz/FutureSDR/tree/feature/flowgraph-macro/futuresdr-macros #[flowgraph(fg)]
{
blk1.out2 > blk2.samples;
blk2 > blk3.input;
blk3.output > blk4;
}; It provides the simplicity you mentioned initially, but not the compile-time verification. And anyway, not all program will be known at compile-time (think graph editor/interpreter as other have started to build). I would also add that what would be great is also to have the capability to check item size at compile-time in some case. |
That sounds like it's breaking Rust philosophy: If your flowgraph internally drops its
Does the flowgraph need to know about these ports at that point? Isn't it enough if it learns about it in
If you put all possible StreamPort types in an enum, or they all have the same common Trait, I believe you can
Maybe the help from somebody with more Rust experience is needed... (ping @bytesnake 😉)
In that case can't we just make
Neat! I was also toying around with a similar thought yesterday |
@JuliDi I completely agree. Recently, I worked on something similar: There is now a @loic-fejoz your comment motivated me pick up your work on macros. They are now merged. Thank you :-) https://www.futuresdr.org/blog/macros/ @nils-werner I know, it's Rust, so why not type everything? And I'd really love to. But in reality that's not always as easy as it may seem from a brief glance over the API.
This is not against any Rust philosophy that I'd know of. It is the exact same thing as adding something to a vector and referring to it with an index in the vector. Also if the outside had a reference, the flowgraph could not use the block, since it couldn't borrow it mutably. Same, if you had an outside mutable reference. So this would go against fundamental Rust concepts. Only option would be to have a Regarding ports: as I mentioned in the previous reply, you cannot make a vector of typed StreamPorts.
I'm not really sure, I get your points. As I said, I'd love to have the API more typed but it's really not trivial. If you have concrete suggestions, your PRs are very welcome! |
Thank you for this awesome project!
@nils-werner asked me to take a look (he knows gnuradio but not rust, for me it's the other way around), I read some hours this morning and wrote things on my mind to issues. I will need more time, and probably prototypes to fully understand. One thing that is not clear to me is what a SDR library makes fundamentally different to computational graphs (such as https://docs.rs/adapton/latest/adapton/#adapton-programming-model), is the edge state a ringbuffer and the computation happens on views instead of passed messages? For inspiration regarding the type description a look at graph librarys (such as petgraph) is sometimes helpful.
you would typical use associated types for this (to reduce type overhead) and add type equality constraints when bounding parameters of chaining methods (like this https://stackoverflow.com/questions/55135637/how-to-require-that-the-associated-types-from-two-traits-are-the-same) The whole story is often more complicated than on first sight but a middle-ground of compile/runtime checking is often achievable (similar to ndarray checking on dimension and storage but not dimension shapes; as this would require sophisticated const generics) Now that GATs are stabilized, we can also bound the associated type's type and make sure that predecessors have the right scalar to operate on, but to understand the effects I need a prototype ...
you can create type list like https://stackoverflow.com/a/40222903 and bound the inner (to have at least sanity checks in place) but rather should implement conversion traits for input/output types and bound them during chaining. An edge state with shared stream and message port could contain a ringbuffer and message queue and implement polling as variant on either one of them (and probably give priority to messages) You could then also require a state with queue for blocks implementing message behaviour.
thanks for doing this in Rust, necessary crab dance 🦀
you could return a struct with block identifier + phantom markers for the input/output type and check the scalar type during connection
no this is hard to do but you could do recursion on associated types (like https://stackoverflow.com/questions/40219725/constructing-hetereogenous-type-lists-in-rust/40222903#40222903) and add associated types for a
or enumeration over the inner type and conversion bounds to the input, but I need a prototype to check
haha I still have a headache when we implemented cross validation in linfa https://github.com/rust-ml/linfa/blob/master/src/dataset/impl_dataset.rs#L843 :D
the macro is great, can you also group them to construct DAGs in blocks?
no because you can't dereference your identifier anymore when the graph drops (and
I will find spare time and experiment a bit, do you have a sample of previous attempts or a design document? I will also probably have questions regarding gnuradio and ask Nils |
I can just say, I looked into other code. And, for example, |
I think this idea (pure compile time checking) is impossible if it is intended to be able to dynamically re/configure from an external source. For instance serializing a flowgraph, or an interactive flowgraph designer (like eSDR). That said, I think a more structured block/port specification interface would be helpful, as opposed to arbitrary strings and integers. |
you can specialize your implementation with a trait object when you want to configure during runtime |
Yes, I suppose it would be possible to support both options somehow. I think I'd need to see an example to really grok it though. |
Hey,
I am new to Rust, so some of the things I am saying may be wrong, or technically impossible. But looking at the example in your README I was a bit surprised to see
because it struck me as a bit "outdated" and error-prone. I would have expected more something like
StreamPort<Complex<f32>>
. This allows the compiler to even catch connection type errors and maybe even infer all connection types just from the output type of the first block?StreamPort
vsMessagePort
), we can infer what kind of connection we are building, so no need forconnect_*()
but oneconnect()
for all port types.Does that make sense, and would that work? As I said, I am new to Rust so my thinking may be wrong here...
The text was updated successfully, but these errors were encountered: