docs: "Interface Introduction" chapter added

This commit is contained in:
Mateusz Pusz
2023-06-23 15:19:08 +02:00
parent 52ce2b21f7
commit 5cf076f82a
2 changed files with 246 additions and 0 deletions

View File

@@ -0,0 +1,245 @@
# Interface Introduction
## New style of definitions
The **mp-units** library decided to use a rather unusual pattern to define entities.
Here is how we define `metre` and `second` [SI](../../../appendix/glossary/#si) base units:
```cpp
inline constexpr struct metre : named_unit<"m", kind_of<isq::length>> {} metre;
inline constexpr struct second : named_unit<"s", kind_of<isq::time>> {} second;
```
Please note that the above reuses the same identifier for a type and its object. The rationale
behind this is that:
- Users always work with objects and never have to spell such a type name.
- The types appear in the compilation errors and during debugging.
!!! info
To improve compiler errors' readability and make it easier to correlate them with
a user's written code, a new idiom in the library is to use the same identifier for
a type and its instance.
## Entities composability
Many physical units libraries (in C++ or any other programming language) assign strong types
to library entities (i.e. derived units). While `metre_per_second` as a type may not look too
scary, consider, for example, units of angular momentum. If we followed this path, its
[coherent unit](../../../appendix/glossary/#coherent-derived-unit) would look like
`kilogram_metre_sq_per_second`. Now, consider how many scaled versions of this unit would you
predefine in the library to ensure that all users are happy with your choice?
How expensive would it be from the implementation point of view?
What about potential future standardization efforts?
This is why in **mp-units**, we put a strong requirement to make everything as composable as
possible. For example, to create a quantity with a unit of speed, one may write:
```cpp
quantity<si::metre / si::second> q;
```
In case you use such an unit often and would prefer to have a handy helper for it, you can
always do something like this:
```cpp
constexpr auto metre_per_second = si::metre / si::second;
quantity<metre_per_second> q;
```
or choose any shorter identifier of your choice.
Coming back to the angular momentum case, thanks to the composability of units, a user can
create such a quantity in the following way:
```cpp
using namespace mp_units::si::unit_symbols;
auto q = la_vector{1, 2, 3} * isq::angular_momentum[kg * m2 / s];
```
It is a much better solution. It is terse and easy to understand. Please also notice how
easy it is to obtain any scaled version of such a unit (i.e. `mg * square(mm) / min`)
without having to introduce hundreds of types to predefine them.
## Expression templates
The previous chapter provided a rationale for not having predefined types for derived entities.
In many libraries, such an approach results in long and unreadable compilation errors, as
framework-generated types are typically far from being easy to read and understand.
The **mp-units** library greatly improves the user experience by extensively using
expression templates. Such expressions are used consistently throughout the entire library
to describe the results of:
- [dimension equation](../../../appendix/glossary/#dimension-equation) - the result is put into
the `derived_dimension<>` class template
- [quantity equation](../../../appendix/glossary/#quantity-equation) - the result is put into
the `derived_quantity_spec<>` class template
- [unit equation](../../../appendix/glossary/#unit-equation) - the result is put into
the `derived_unit<>` class template
For example, if we take the above-defined base units and put the results of their division into
the quantity class template like this:
```cpp
quantity<metre / second> q;
```
we will observe the following type in the debugger
```
(gdb) ptype q
type = class mp_units::quantity<mp_units::derived_unit<metre, mp_units::per<second>>(), double> [with Rep = double] {
```
The same type identifier will be visible in the compilation error (in case it happens).
!!! info
Expressions templates are extensively used throughout the library to improve the readability
of the resulting types.
### Identities
As mentioned above, equations can be done on dimensions, quantities, and units. Each such domain must
introduce an identity object that can be used in the resulting expressions. Here is the list of
identities used in the library:
| Domain Concept | Identity |
|----------------|:---------------:|
| `Dimension` | `dimension_one` |
| `QuantitySpec` | `dimensionless` |
| `Unit` | `one` |
In the equations, a user can refer to an identity object either explicitly:
```cpp
constexpr auto my_unit = one / second;
```
or implicitly:
```cpp
constexpr auto my_unit = 1 / second;
```
Both cases with result in the same expression template being generated and put into the wrapper
class template.
### Supported operations and their results
There are only a few operations that one can do on such entities and the result of each of them has
its unique representation in the library:
| Operation | Resulting template expression arguments |
|:---------------------------------------------:|:---------------------------------------:|
| `A * B` | `A, B` |
| `B * A` | `A, B` |
| `A * A` | `power<A, 2>` |
| `{identity} * A` | `A` |
| `A * {identity}` | `A` |
| `A / B` | `A, per<B>` |
| `A / A` | `{identity}` |
| `A / {identity}` | `A` |
| `{identity} / A` | `{identity}, per<A>` |
| `pow<2>(A)` | `power<A, 2>` |
| `pow<2>({identity})` | `{identity}` |
| `sqrt(A)` or `pow<1, 2>(A)` | `power<A, 1, 2>` |
| `sqrt({identity})` or `pow<1, 2>({identity})` | `{identity}` |
### Simplifying the resulting expression templates
To limit the length and improve the readability of generated types, there are many rules to simplify
the resulting expression template.
1. **Ordering**
The resulting comma-separated arguments of multiplication are always sorted according to
a specific predicate. This is why:
```cpp
static_assert(A * B == B * A);
static_assert(std::is_same_v<decltype(A * B), decltype(B * A)>);
```
This is probably the most important of all steps, as it allows comparing types and enables the rest of
simplification rules.
2. **Aggregation**
In case two of the same identifiers are found next to each other on the argument list they
will be aggregated in one entry:
| Before | After |
|:--------------------------------:|:----------------:|
| `A, A` | `power<A, 2>` |
| `A, power<A, 2>` | `power<A, 3>` |
| `power<A, 1, 2>, power<A, 2>` | `power<A, 5, 2>` |
| `power<A, 1, 2>, power<A, 1, 2>` | `A` |
3. **Simplification**
In case two of the same identifiers are found in the numerator and denominator argument lists;
they are being simplified into one entry:
| Before | After |
|:---------------------:|:--------------------:|
| `A, per<A>` | `{identity}` |
| `power<A, 2>, per<A>` | `A` |
| `power<A, 3>, per<A>` | `power<A, 2>` |
| `A, per<power<A, 2>>` | `{identity}, per<A>` |
4. **Repacking**
In case an expression uses two results of other operations, the components of its arguments are repacked
into one resulting type and simplified there.
For example, assuming:
```cpp
constexpr auto X = A / B;
```
then:
| Operation | Resulting template expression arguments |
|:---------:|:---------------------------------------:|
| `X * B` | `A` |
| `X * A` | `power<A, 2>, per<B>` |
| `X * X` | `power<A, 2>, per<power<B, 2>>` |
| `X / X` | `{identity}` |
| `X / A` | `{identity}, per<B>` |
| `X / B` | `A, per<power<B, 2>>` |
## Example
Thanks to all of the features described above, a user may write the code like this one:
```cpp
using namespace mp_units::si::unit_symbols;
auto speed = 60. * isq::speed[km / h];
auto duration = 8 * s;
auto acceleration = speed / duration;
std::cout << "acceleration: " << acceleration << " (" << acceleration[m / s2] << ")\n";
```
The `acceleration`, being the result of the above code, has the following type
(after stripping the `mp_units` namespace for brevity):
```text
quantity<reference<derived_quantity_spec<isq::speed, per<isq::time>>{}, derived_unit<si::kilo_<si::metre{}>, per<non_si::hour, si::second>>{}>{}, int>
```
and the text output presents:
```text
acceleration: 7.5 km h⁻¹ s⁻¹ (2.08333 m/s²)
```

View File

@@ -106,6 +106,7 @@ nav:
- Terms and Definitions: users_guide/terms_and_definitions.md
- Framework Basics:
- Basic Concepts: users_guide/framework_basics/basic_concepts.md
- Interface Introduction: users_guide/framework_basics/interface_introduction.md
- Systems of Quantities: users_guide/framework_basics/systems_of_quantities.md
- Systems of Units: users_guide/framework_basics/systems_of_units.md
- Value Conversions: users_guide/framework_basics/value_conversions.md