-
Notifications
You must be signed in to change notification settings - Fork 680
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
Vision: Note on the API design that involves code auto-generation #32
Comments
I agree that this is a good place to use a builder pattern and I really like the syntax. One key requirement when doing something like this is to make sure it is well documented. This can be tricky with builder pattern because you absolutely need concrete examples since the normal way people poke around (seeing what methods exist on a thing) is more difficult with the builder pattern (i.e., it might be tricky to realize that you have to type |
Just to clarify, I did not mean to fix only this particular macro. The builder pattern is also not as important, as my main point not to conjure identifiers out of thin air. I do not exclude that there could be other approaches, but that is also not the point of this post. To address your comment, though, I agree that when you split a single API into a set of APIs, which is the case with the builder pattern, then it will be slightly trickier to understand. I don't think it's the end of the world, though: when you write runtime API unless you are directed to do that explicitly (somebody metaphorically types with your fingers), you have very likely seen some examples of how to deal with the particular API. Therefore, you probably know that there is the Now, how they interact and what they do would not be particularly clear IMO. I think a good and sufficient solution would be to cross-link every part of the picture in the docs to a single place, and then explain there what is happening. One potential way to do this, is to leave some documentation on the generated trait, that explains how to use it briefly, with an example, mentioning all kinds of different caveats. One caveat that could be mentioned is in the example below:
No work will be done in case But TBH, I don't think it's worth discussing. IMO APIs at this level of user-facing-ness should have this kind of documentation by default. |
I thought about this already since quite some time. Aka that this
Then there is also no problem around the documentation as there would be visible from the a normal trait. |
I do believe that this is the direction that we were supposed to move towards (i.e. no magical identifiers coming out of nowhere), but the one macro that gives me pause is |
You would need to change the syntax to make this more obvious. However, I don't have any idea about a good syntax. |
Just want to double down in this and add that coming out of the academy, it was extremely difficult to explain these magical keywords to students. For #[frame::contruct_rutnime]
mod runtime {
// The rest, like `AllPalletsReserved` and such have to be
// unfortunately aut-generated, or better, deprecated.
#[frame::pallets]
type AllPallets = (
frame_system::pallet,
pallet_balances::pallet,
pallet_staking::pallet,
pallet_session::pallet,
);
// don't think there is much to this.
#[frame::runtime]
pub struct Runtime;
// Auto-generate call from `AllPallets`
#[frame::call]
pub enum RuntimeCall = ..AllPallets;
// Alternative to the spread syntax above: Explicitly build an enum with the given
// pallet paths.
//
// This is very dangerous, only for experts. Probably no real use case for it either..
#[frame::event]
pub enum RuntimeEvent = (frame_system, pallet_staking);
#[frame::origin]
pub enum Origin = ..AllPallets;
// Have not thought it through, but see https://github.com/paritytech/substrate/issues/13463#issuecomment-1464013060
#[frame::ext_builder]
pub struct ExtBuilder {...}
}
pub use runtime::{Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin}; Having written the above: I think the main block in turning a lot of the FRAME primitives to builders patters (which are arguably a lot nicer) is the over-use of types. Everything in FRAME is types, not a value, and types must be known at compile-time. Of course, the benefit is that we are avoiding a lot of dynamic dispatches by using types rather than values for calls and such. The best example of this is the choice of
Against
If something like I have never had the time to explore this deeply to know how easier life would get if we could easily use the builder pattern, but I highly doubt of the micro performance gain here is actually making life any easier for anyone. This is, in my opinion, that we had to create so many macros in the first place. Perhaps instead of bending our neck to make a macro that mimics real-rust so much, we should consider such lower-level approaches. |
How should this even look like? I mean what you are proposing there is a completely different DSL. One of the main points we are only operating on types is that the state is the storage and we don't use the memory to store any state. I also don't really get what your builder pattern should look like here? What would it do? If you really want to have some builder pattern, we could also make it work on the type level. |
Just wanted to give a huge +1 to the proposed syntax for runtime construction that @kianenigma has suggested here. It was and is a big struggle for us as well to always track down all the identifiers that the |
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Bumps [vergen](https://github.com/rustyhorde/vergen) from 3.0.4 to 3.1.0. - [Release notes](https://github.com/rustyhorde/vergen/releases) - [Commits](https://github.com/rustyhorde/vergen/commits/v3.1.0) Signed-off-by: dependabot-preview[bot] <[email protected]> Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
* Nits * Initial network transaction manager * Answer the peer's transaction request * Remove timeout transactions * Add rpc `subcoin_sendTransaction` * Add transaction from peer * Add rpc subcoin_getRawTransaction * Add rpc subcoin_decodeRawTransaction * Fix bitcoin transaction broadcasting * Introduce SendTransactionResult * serde camelCase * Nit
One of the best things about Rust is the balance between explicit and implicit things. See here to get the mental framework here. One example I want to highlight is importing things.
If you worked with languages like C, to print something, you would write
#include <stdio.h>
, and it would pullprintf
among other things. This can be very convenient for somebody who already knows C very well. They already know thatprintf
comes fromstdio
. In other languages, such as Java or Rust, you have to manually specify everything you import with the fully qualified path, one by one. Additionally, those languages support the wildcard import,*
, but it is frowned upon.I consider that Rust made here a very good decision. It might be annoying to list every possible import, but you get a lot more in return. Let's turn the example upside down. Imagine you are reading code and seeing a previously unseen identifier. In Rust, you would need to grep within the file that identifier, and chances are you will find either an import or the declaration1. Compare that to C: you will have to check every included header. Moreover, headers can include other headers as well. At that point, you will have to resort to a project-wide grep. C makes it easier to write code, and Rust optimizes for reading.
Now, how this translates to the API design? We have some macros that, unfortunately, do not follow the same spirit of API design. The worst offender so far is the runtime API.
Take for example the declaration of
apply_extrinsic
:https://github.com/paritytech/substrate/blob/bd2a876e4c0e79b6e2ab6b0a92c038304a64b20f/primitives/block-builder/src/lib.rs#L27-L33
This will generate code that will implement this trait by calling into the Substrate Runtime at the current block (apply_extrinsic). Besides that it will generate a function (apply_extrinsic_with_context) for calling that Runtime API at a specific context, mostly used to call it at a state root other than the last best.
Besides that, I am aware that there are functions that allow calling the Runtime API of a previous version. Here is the example,
https://github.com/paritytech/substrate/blob/bd2a876e4c0e79b6e2ab6b0a92c038304a64b20f/client/block-builder/src/lib.rs#L212-L218
Now, imagine a person reading this code with fresh eyes. If they are not aware of this code generation, and they try to grep
fn apply_extrinsic_before_version_6_with_context
they will end up with nothing and will end up very confusing.I propose the following guideline: avoid pulling new identifiers from thin air. Generally, try to avoid changing the identifiers or signatures provided by the user.
In particular, I think the following is the good shape of the API:
It does not matter much how those are grouped, you can imagine
at
andcontext
to be one call, and/or they can be the final calls (i.e. instead of the separatecall
call). That's not the point. The point is, that theapply_extrinsic
has the same name asBlockBuilder::apply_extrinsic
.If you continue along those lines, you can arrive at an even better fitting API:
This shape of the API has the same name and the same signature. Unfortunately, it may be less pragmatic to implement since
at
coming from multiple implementations will collide, but still may be worth pursuing.Footnotes
Yes, I know there are exceptions, like inherent methods or traits. Or maybe when IDE is used. But bear with me, I still believe the point stands. ↩
The text was updated successfully, but these errors were encountered: