Skip to content

Commit 952b4db

Browse files
authored
Write Architecture Notes Documentation (#589)
* Add documentation for type traits * Add documentation for getter traits * Add documentation for #[blanket_trait] * Add documentation for check components * Drafting field types * Restore lost writings * Finish writing enum section * Writing preset chapter * More writings for preset * Finish writing the preset chapter * Add chapter for UseDelegate * Fix typos * Fix typos
1 parent 43fabf6 commit 952b4db

File tree

9 files changed

+1655
-1
lines changed

9 files changed

+1655
-1
lines changed

crates/cosmos/cosmos-relayer/src/contexts/chain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ check_components! {
372372
]: [
373373
CosmosChain,
374374
AnyCounterparty,
375-
]
375+
],
376376
}
377377
}
378378

docs/notes/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Hermes SDK Architecture Notes
2+
3+
## Core Concepts
4+
5+
- [Type Traits](./type-traits.md)
6+
- [Getter Traits](./getter-traits.md)
7+
- [Blanket Traits](./blanket-traits.md)
8+
- [Check Components](./check-components.md)
9+
- [Field Types](./field-types.md)
10+
- [Presets](./presets.md)
11+
- [`UseDelegate`](./use-delegate.md)

docs/notes/blanket-traits.md

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# Blanket Traits
2+
3+
The `#[blanket_trait]` macro is provided to simplify the implementation of traits that are meant to only contain blanket implementations and not implemented by others. Without the macro, we would first need to define the trait, and then define the blanket `impl` for that trait. But with `#[blanket_trait]`, the two definitions can be merged into one.
4+
5+
## Combining Multiple Traits
6+
7+
The first use of `#[blanket_trait]` is to define trait aliases that combine multiple traits into one.
8+
9+
For example, if we want to combine a `Foo` trait and a `Bar` trait together to become a `FooBar` trait, a manual approach would require the following definition:
10+
11+
```rust
12+
pub trait FooBar: Foo + Bar {}
13+
14+
impl<Context> FooBar for Context
15+
where
16+
Context: Foo + Bar,
17+
{}
18+
```
19+
20+
But with the use of `#[blanket_trait]`, the whole definition can be shorten to:
21+
22+
```rust
23+
#[blanket_trait]
24+
pub trait FooBar: Foo + Bar {}
25+
```
26+
27+
Behind the scene, `#[blanket_trait]` generates a blanket implementation that can minimally satisfy the constraint of the trait definition.
28+
29+
## Blanket Methods
30+
31+
We can also use `#[blanket_trait]` to provide blanket implementations that contain methods, by implementing the given methods in the trait.
32+
33+
For example, suppose we want to implement a blanket implementation of `foobar()` that calls both `foo()` and `bar()`, a manual implementation would require the following to be written:
34+
35+
36+
```rust
37+
pub trait FooBar: Foo + Bar {
38+
fn foobar();
39+
}
40+
41+
impl<Context> FooBar for Context
42+
where
43+
Context: Foo + Bar,
44+
{
45+
fn foobar(&self) {
46+
self.foo().bar()
47+
}
48+
}
49+
```
50+
51+
With `#[blanket_trait]`, the same definition can be shorten to:
52+
53+
```rust
54+
#[blanket_trait]
55+
pub trait FooBar: Foo + Bar {
56+
fn foobar(&self) {
57+
self.foo().bar()
58+
}
59+
}
60+
```
61+
62+
## Associated Type Alias
63+
64+
The use of `#[blanket_trait]` can allow us to also more easily define associated type aliases that can be used to simplify the reference to a given abstract type, especially when it is parameterized through a generic type.
65+
66+
For example, given the following type trait:
67+
68+
```rust
69+
#[cgp_type]
70+
pub trait HasChainTypeAt<I> {
71+
type Chain;
72+
}
73+
```
74+
75+
It can be quite tedious to refer to the `Chain` context at each index, such as `<Context as HasChainTypeAt<Index<0>>>::Chain` and `<Context as HasChainTypeAt<Index<1>>>::Chain`. We may want to define _associated type aliases_ `FirstChain` and `SecondChain`, so that we can refer to the first and second chains more easily in our code. A manual implementation of such alias would be as follows:
76+
77+
```rust
78+
pub trait HasTwoChains:
79+
HasChainTypeAt<Index<0>, Chain = Self::FirstChain>
80+
+ HasChainTypeAt<Index<1>, Chain = Self::SecondChain>
81+
{
82+
type FirstChain;
83+
84+
type SecondChain;
85+
}
86+
87+
impl<Context, FirstChain, SecondChain>
88+
HasTwoChains for Context
89+
where
90+
Context:
91+
HasChainTypeAt<Index<0>, Chain = FirstChain>
92+
+ HasChainTypeAt<Index<1>, Chain = SecondChain>,
93+
{
94+
type FirstChain = FirstChain;
95+
96+
type SecondChain = SecondChain;
97+
}
98+
```
99+
100+
The way we understand how the associated type alias works is as follows:
101+
102+
- The trait `HasTwoChains` contains two associated types, `FirstChain` and `SecondChain`, which are unspecified at the trait definition.
103+
- The trait has a supertrait `HasChainTypeAt<Index<0>, Chain = Self::FirstChain>`. This means that when the trait is used, the type `<Self as HasChainTypeAt<Index<0>>>::Chain` _must_ be the same as `Self::FirstChain`.
104+
- Similarly, the supertrait `HasChainTypeAt<Index<0>, Chain = Self::FirstChain>` indicates that `<Self as HasChainTypeAt<Index<1>>>::Chain` must be the same as `Self::SecondChain`.
105+
- In the blanket implementation, we introduce two generic parameters, `FirstChain` and `SecondChain`
106+
- We bind `FirstChain` in `HasChainTypeAt<Index<0>, Chain = FirstChain>`, meaning that we now just refer to `<Context as HasChainTypeAt<Index<0>>>::Chain` as `FirstChain`.
107+
- Similarly, with the binding of `SecondChain` in `HasChainTypeAt<Index<1>, Chain = SecondChain>`, it means that we now just refer to `<Context as HasChainTypeAt<Index<1>>>::Chain` as `SecondChain`.
108+
- We then define the associated type `FirstChain` to be just `FirstChain`, and similarly for `SecondChain`.
109+
- This means that effectively, we are just aliasing the associated types with new names.
110+
111+
In case if it is still confusing to you, the same blanket `impl` above can be rewritten as follows, which may be more verbose but perhaps less confusing:
112+
113+
```rust
114+
impl<Context> HasTwoChains for Context
115+
where
116+
Context:
117+
HasChainTypeAt<Index<0>>
118+
+ HasChainTypeAt<Index<1>>,
119+
{
120+
type FirstChain = <Context as HasChainTypeAt<Index<0>>>::Chain;
121+
122+
type SecondChain = <Context as HasChainTypeAt<Index<1>>>::Chain;
123+
}
124+
```
125+
126+
Basically, the two implementations are equivalent, just written in different styles.
127+
128+
But the key takeaway is that with `#[blanket_trait]`, we do not need to hand write any of the blanket impls, and it would instead be automatically derived for us:
129+
130+
131+
```rust
132+
#[blanket_trait]
133+
pub trait HasTwoChains:
134+
HasChainTypeAt<Index<0>, Chain = Self::FirstChain>
135+
+ HasChainTypeAt<Index<1>, Chain = Self::SecondChain>
136+
{
137+
type FirstChain;
138+
139+
type SecondChain;
140+
}
141+
```
142+
143+
The main requirement for the use of associated type aliases is that the associated type must be bound to some other associated type inside the supertraits. In this case, we bind both `Self::FirstChain` and `Self::SecondChain` in the trait alias, and so `#[blanket_trait]` is able to generate the blanket implementation correctly for us.

0 commit comments

Comments
 (0)