From 5cf076f82a1e68a36e71f7222d3c5a3a27df2527 Mon Sep 17 00:00:00 2001 From: Mateusz Pusz Date: Fri, 23 Jun 2023 15:19:08 +0200 Subject: [PATCH] docs: "Interface Introduction" chapter added --- .../interface_introduction.md | 245 ++++++++++++++++++ mkdocs.yml | 1 + 2 files changed, 246 insertions(+) create mode 100644 docs/users_guide/framework_basics/interface_introduction.md diff --git a/docs/users_guide/framework_basics/interface_introduction.md b/docs/users_guide/framework_basics/interface_introduction.md new file mode 100644 index 00000000..d3657014 --- /dev/null +++ b/docs/users_guide/framework_basics/interface_introduction.md @@ -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> {} metre; +inline constexpr struct second : named_unit<"s", kind_of> {} 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 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 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 q; +``` + +we will observe the following type in the debugger + +``` +(gdb) ptype q +type = class mp_units::quantity>(), 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` | +| `{identity} * A` | `A` | +| `A * {identity}` | `A` | +| `A / B` | `A, per` | +| `A / A` | `{identity}` | +| `A / {identity}` | `A` | +| `{identity} / A` | `{identity}, per` | +| `pow<2>(A)` | `power` | +| `pow<2>({identity})` | `{identity}` | +| `sqrt(A)` or `pow<1, 2>(A)` | `power` | +| `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); + ``` + + 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, power` | `power` | + | `power, power` | `power` | + | `power, power` | `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` | `{identity}` | + | `power, per` | `A` | + | `power, per` | `power` | + | `A, per>` | `{identity}, per` | + +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, per` | + | `X * X` | `power, per>` | + | `X / X` | `{identity}` | + | `X / A` | `{identity}, per` | + | `X / B` | `A, per>` | + + +## 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>{}, derived_unit, per>{}>{}, int> +``` + +and the text output presents: + +```text +acceleration: 7.5 km h⁻¹ s⁻¹ (2.08333 m/s²) +``` diff --git a/mkdocs.yml b/mkdocs.yml index beb34c1c..51561fa7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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