Skip to content

Commit 508cd44

Browse files
committed
Add section for problems solved by CGP
1 parent 5029582 commit 508cd44

File tree

1 file changed

+141
-20
lines changed

1 file changed

+141
-20
lines changed

content/_index.md

Lines changed: 141 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -49,16 +49,37 @@ during application runtime.
4949
## No-Std Friendly
5050

5151
CGP makes it possible to build _fully-abstract programs_ that can be defined
52-
with _zero dependencies_. This allows such programs to be instantiated with
53-
specialized-dependencies in no-std environments, such as on embedded systems,
54-
operating system kernels, or Wasm sandboxes.
52+
with _zero concrete dependencies_ (aside from other abstract CGP components).
53+
What this means is that dependencies including I/O, runtime, cryptographic
54+
operations, encoding schemes, can all be abstracted away from the core logic
55+
of the application.
56+
57+
This allows the application core logic to be instantiated with
58+
specialized dependencies in no-std environments, such as on embedded systems,
59+
operating system kernels, sandboxed environments like Wasm, and symbolic
60+
execution environments like Kani.
5561

5662
## Zero-Cost Abstraction
5763

5864
Since all CGP features work only at compile-time, it provides the same
5965
_zero-cost abstraction_ advantage as Rust. Applications do not have to sacrifice
6066
any runtime overhead for using CGP in the code base.
6167

68+
# Current Status
69+
70+
As of end of 2024, CGP is still in _early-stage_ development, with many
71+
rough edges in terms of documentation, tooling, debugging techniques,
72+
community support, and ecosystem.
73+
74+
As a result, you are advised to proceed _at your own risk_ on using CGP in
75+
any serious project. Note that the current risk of CGP is _not_ technical,
76+
but rather the limited support you may get when encoutering any challenge
77+
or difficulty in learning or using CGP.
78+
79+
Currently, the target audience for CGP are primarily early adopters and
80+
[contributors](#contribution), preferrably with strong background in
81+
_functional programming_ and _type-level programming_.
82+
6283
# Hello World Example
6384

6485
We will demonstrate various concepts of CGP with a simple hello world example.
@@ -109,7 +130,7 @@ where
109130
}
110131
```
111132

112-
The provider `GreetHello` is implemented as a struct, and implements
133+
The provider `GreetHello` is defined as a struct, and implements
113134
the provider trait `Greeter`. It is implemented as a
114135
_context-generic provider_ that can work with any `Context` type,
115136
but with additional constraints (or dependencies) imposed on the
@@ -128,14 +149,14 @@ the struct.
128149

129150
The `HasField` trait provides a `get_field` method,
130151
which can be used to access a field value. The type
131-
`PhantomData::<symbol!("name")>` is passed to `get_field` to infer
152+
`PhantomData::<symbol!("name")>` is passed to `get_field` to help infer
132153
which field we want to read, in case if there are more than one
133154
field in scope.
134155

135156
Notice that with the separation of provider trait from consumer trait,
136157
multiple providers like `GreetHello` can _all_ have generic implementation
137158
over any `Context`, without causing any issue of overlapping implementation
138-
imposed by Rust's trait system.
159+
that is usually imposed by Rust's trait system.
139160

140161
Additionally, the provider `GreetHello` can require additional
141162
constraints from `Context`, without those constraints bein present
@@ -220,6 +241,7 @@ is implemented by `Person` via `PersonComponents`, which implements
220241
`Greeter` via delegation of `GreeterComponent` to `GreetHello`,
221242
which implements `Greeter` given that `Person` implements
222243
`HasField<symbol!("name"), Field: Display>`.
244+
That is a lot of indirection going on!
223245

224246
Hopefully by the end of this tutorial, you have gotten a sense of how
225247
it is like to program in CGP.
@@ -228,20 +250,119 @@ by CGP, and what else we can do with CGP.
228250
You can continue and find out more by reading the book
229251
[Context-Generic Programming Patterns](https://patterns.contextgeneric.dev/).
230252

231-
# Current Status
232-
233-
As of end of 2024, CGP is still in _early-stage_ development, with many
234-
rough edges in terms of documentation, tooling, debugging techniques,
235-
community support, and ecosystem.
236-
237-
As a result, you are advised to proceed _at your own risk_ on using CGP in
238-
any serious project. Note that the current risk of CGP is _not_ technical,
239-
but rather the limited support you may get when encoutering any challenge
240-
or difficulty in learning or using CGP.
241-
242-
Currently, the target audience for CGP are primarily early adopters and
243-
[contributors](#contribution), preferrably with strong background in
244-
_functional programming_.
253+
# Problems Solved
254+
255+
Here are some example common problems in Rust that CGP helps to solve.
256+
257+
## Error Handling
258+
259+
Instead of choosing a specific error crate like `anyhow` or `eyre`, the
260+
CGP traits `HasErrorType` and `CanRaiseError` can be used to decouple
261+
the application core logic from error handling.
262+
Concrete applications can freely choose specific error library, as well as
263+
suitable strategies such as whether to include stack traces inside the error.
264+
265+
## Async Runtime
266+
267+
Instead of choosing a specific runtime crate like `tokio` or `async-std`,
268+
CGP allows application core logic to depend on an abstract runtime context
269+
that provide features that only the application requires.
270+
271+
Compared to monolithic runtime traits, an abstract runtime context in
272+
CGP does _not_ require comprehensive or up front design of all possible
273+
runtime features application may need. As a result, CGP makes it easy
274+
to switch between concrete runtime implementations, depending on which
275+
runtime feature the application actually uses.
276+
277+
## Overlapping Implementations
278+
279+
A common frustration among Rust programmers is the restrictions on
280+
potentially overlapping implementations of traits.
281+
A common workaround for the limitation is to use newtype wrappers.
282+
However, the wrapping can become complicated, when there are multiple
283+
composite types that need to be extended.
284+
285+
As Rust requires a crate to own either the type or the trait for a
286+
trait implementation, this often places significant burden on the
287+
author that defines a new type to implement all possible common traits
288+
their users may need. This often leads to type definitions accompanied
289+
by overly bloated implementations of traits such as `Eq`, `Clone`,
290+
`TryFrom`, `Hash`, and `Serialize`. But even with great care, the library
291+
could still get requests from users to implement one of the less common
292+
traits that only the owner of the type can implement.
293+
294+
With the introduction of _provider traits_, CGP removes the restrictions
295+
on overlapping implementations altogether. Both owner and non-owners
296+
of a type can define a custom implementation for the type. When multiple
297+
provider implementations are available, users can choose one of them, and
298+
easily wire up the provider using CGP constructs.
299+
300+
CGP also prefer the use of _abstract types_ over newtype wrappers. For
301+
example, a type like `f64` can be used directly to for both
302+
`Context::Distance` and `Context::Weight`, with the associated types
303+
still treated as different types inside the abstract code. CGP also
304+
makes it possible for specialized providers to be implemented, even
305+
if the crate do not own the primitive type `f64` or the provider trait.
306+
307+
## Dynamic Dispatch
308+
309+
A common attempt for newcomers to support polymorphism in Rust code is
310+
to use dynamic dispatch in the for of `dyn Trait` objects. However, this
311+
severely limits what can be done in the code to a limited subset of
312+
_object-safe_ features in Rust. Very often, this limitation can be
313+
infectious to the entire code base, and require non-trivial workaround
314+
on non-object-safe constructs such as `Clone`.
315+
316+
Even when dynamic dispatch is not used, many Rust programmers also resort
317+
to ad-hoc polymorphism, by defining enums to represent all possible variants
318+
of types that may be used in the application. This leads to many `match`
319+
expressions scattered across the code base, making it challenging to
320+
decouple the code for each branch. Furthermore, this approach makes it
321+
very difficult to add new variants to the enum, as all branches have to
322+
be updated, even when the variant is only used in a small part of the code.
323+
324+
CGP provides multiple ways to solve the dynamic dispatch problem, by leaving
325+
the "assembly" of the collection of variants to the concrete context.
326+
Meanwhile, the core application logic can be written to be generic over
327+
the context, together with the assocaited type that represents the abstract enum.
328+
CGP also enables powerful data-generic pattern that allows providers of
329+
each variant to be implemented separately, and then be combined to work
330+
with enums that contain any combination of the variants.
331+
332+
## Monolithic Traits
333+
334+
Even without CGP, Rust' trait system already provides powerful ways for
335+
programmers to build abstractions that would otherwise not be possible
336+
in other mainstream languages. One of the best practices is similar
337+
to CGP, which is to write abstract code that is generic over a context
338+
type, except that there is an implicit trait bound that is always
339+
tied to the generic context.
340+
341+
Unlike CGP, the trait in this pattern is often designed as a monolithic
342+
trait that contains _all_ dependencies that the core application may need.
343+
This is because without CGP, an abstract caller would have to also include
344+
all the trait bounds that are specified by the generic functions it calls.
345+
This means that any extra generic trait bound would easily propagate to
346+
the entire code base. And when that happens, developers would just combine
347+
all trait bounds into one monolithic trait for convenience sake.
348+
349+
Monolithic traits can easily become the bottleneck that prevents large projects
350+
from scaling further. It is not uncommon for monolithic traits to be bloated
351+
with dozens, if not hundreds, of methods and types. When that happens, it
352+
becomes increasingly difficult to introduce new implementations to such
353+
monolithic trait.
354+
With the current practices in Rust, it is also challenging to decouple or
355+
break down such monolithic trait to multiple smaller traits.
356+
357+
CGP offers significant improvement over this original design pattern,
358+
and makes it possible to write abstract Rust code without the risk of
359+
introducing a giant monolithic trait.
360+
CGP makes it possible to to break monolithic traits down to many
361+
small traits, which in fact, could and _should_ be as small as _one_
362+
method or type per trait. This is made possible thanks to the
363+
dependency injection pattern used by CGP, which allows implementations
364+
only introduce the minimal trait bounds they need directly within the
365+
body of the implementation.
245366

246367
# Getting Started
247368

0 commit comments

Comments
 (0)