This is part twentytwo in a series on the language spec for the Merg-E Domain Specific Language for the InnuenDo Web 3.0 stack. Where most previous posts were about v0.3, this is the third post that is about the v0.4 version of the spec. I'll add more parts to the below list as the spec progresses:
- part 1 : coding style, files, merging, scoping, name resolution and synchronisation
- part 2 : reverse markdown for documentation
- part 3 : Actors and pools.
- part 4 : Semantic locks, blockers, continuation points and hazardous blockers
- part 5 : Semantic lexing, DAGs, prune / ent and alias.
- part 6 : DAGs and DataFrames as only data structures, and inline lambdas for pure compute.
- part 7 : Freezing
- part 8 : Attenuation, decomposition, and membranes
- part 9 : Sensitive data in immutables and future vault support.
- part 10 : Scalars and High Fidelity JSON
- part 11 : Operators, expressions and precedence.
- part 12 : Robust integers and integer bitwidth generic programming
- part 13 : The Merg-E ownership model, capture rules, and the --trustmebro compiler flag.
- part 14 : Actorcitos and structural iterators
- part 15 : Explicit actorcitos, non-inline structural iterators, runtimes, and abstract scheduler pipeline.
- part 16 : async functions and resources and the full use of InnuenDo VaultFS
- part 17: RAM-Points, RAM-points normalization bag, and the quota-membrane.
- part 18: Literal operators & Rational and Complex numbers.
- part 19: Interaction between operators, integer bitwidth generics, and the full numeric type-system.
- part 20 (v0.4): Compile-time dimensional analysis, SI/Planck units and the scaling literal operator.
- part 21 (v0.4): Tensors and tensor literals.
- part 22 (v0.4): Deprecating float/complex for qfloaf/qcomplex for full dimensional type-safety.
In this post I'm doing a short follow up on my previous post on dimensional analysis on floating point type families float and complex.
As you may have read, in my post on dimensional analysis, I was hesitating if I needed to make dimensions mandatory for all floating point types. I have decided that yes I should, Merg-E should be radically safe by default and explicit where being implicit comes with risks.
However looking into this, it's not simple because we also need to take into account how we handle operator demotion on exact types.
How operator demotion fits in
In part 19 of this series we saw that exact types will promote when used with operators, that the result of an int64 times a whole32 plus an int16 is an int128, and how virtual compile time types somewhat reduce the bitwidth explosion. We also saw the absolute promotion limits stemming from virtual types like Vint20740. The biggest int family runtime type is int16384, and as Vint20740 has no other place to promote to, it demotes to float256, the highest precision floating point.
So if we say that all float and complex based values need dimensional annotations and the un-demoted exact countable types never have such dimensional annotations, how do we keep unintended dimension type errors from occurring?
We do that by leveraging a unit static field we already discussed, the sys or system field. So far we discussed SI and PLANCK as possible system designations. We add a third value to our spec: DEMOTE.
inert whole128 a = 4041424344 * 4041424344 * 4041424344 * 4041424344;
inert whole1024 b = ( a + 1 ) * a * a * a;
inert int8192 c = b * b * b * b;
inert quantity::<tensor::<float256,0>, unit::<sys: DEMOTE>> d = c * c + 1;
So what is the purpose of this? Well, as we discussed earlier, quantities with a defined sys parameter don't mix in expression with quantities with a different defined sys parameter. That was the reason we discussed using an empty sys field for dimensionless quantities, so they work well with both PLANCK and SI quantities.
And right here that is exactly what we want. The result of a demotion to a float or complex quantity can only be used in expressions with non dimensional quantities, and the result of these expressions also can only be used with non dimensional quantities. This is the highest dimensional safety we think we can achieve in Merg-E. Remember, we are mixing two worlds with demotions, and this is the only dimensional way to do that without implicit anything.
From float/complex to qfloat/qcomplex with modifiers
In v0.4 we take the radical step to declare that you can no longer write:
inert float16 pi = 3.140635;
These types can only be used as type in a quantity annotation:
inert quantity::<tensor<float16, 0> unit::<sys: SI>> pi = 3.140635;
This is quite a bit of verbosity. To overcome this verbosity we introduce a language feature called parameterized aliases. Detailed specs of these will come in a later post on the subject, for now we just show how they will work for floats. The base idea is that a type plus a set of modifiers together have an alias, and the modifiers become parameters in the alias. We use this feature to allow us to write something like:
inert nodim qfloat16 pi = 3.140635;
Here qfloat16 is a parameterized alias to something along the lines of quantity::<tensor<float16, 0> unit::<sys: ${SYS}, @{DIMS}>> .
The nodim modifier defines the two alias parameters. ${SYS} and ${DIMS}. This way other modifiers allow us to create shorter notations for common units as well. Some examples
| modifier | SYS | dims |
|---|---|---|
| nodim | ||
| demote | DEMOTE | |
| plancklength | PLANCK | lengh: 1 |
| silength | SI | length : 1 |
| plancktime | PLANCK | time : 1 |
| sitime | SI | time : 1 |
| planckmass | PLANCK | mass: 1 |
| simass | SI | mass : 1 |
| plancktemp | PLANCK | temp: 1 |
| sitemp | SI | temp : 1 |
| planckcurrent | PLANCK | charge : 1, time: -1 |
| sicurrent | SI | current : 1 |
| planckcharge | PLANCK | charge : 1 |
| sicharge | SI | current : 1, time: 1 |
| planckspeed | PLANCK | length: 1, time: -1 |
| sispeed | SI | length: 1, time: -1 |
| planckaccel | PLANCK | length: 1, time: -2 |
| siacceleration | SI | length: 1, time: -2 |
| sisubstance | SI | substance: 1 |
| siintensity | SI | intensity: 1 |
The above table is illustrative for now, a complete version will be part of the language spec but is too long for this blog post series.
You might have noticed the demote modifier. We can use that one in our first bit of code to rewrite:
inert quantity::<tensor::<float256,0>, unit::<sys: DEMOTE>> d = c * c + 1;
as:
inert demote qfloat256 d = c * c + 1;
Some verbosity is left, but this time it's the right amount of verbosity.
Looking at actual quantities, things also become less verbose for most meaningful physical quantities using these parameterized aliases:
inert quantity::<tensor::<float16, 0>, units.meter> height = 1.825;
inert quantity::<tensor::<float16,0>, unit::<sys: PLANCK, length: 1, time: -1>> |||
speed = 0.07;
inert quantity::<tensor::<float16, 0>,unit::<sys: SI, length:1, time:-2>> g = 9.80665;
inert quantity::<tensor::<float256, 0>, units.meter> tinybit = 1.437 þ -9;
becomes
inert silength qfloat16 height = 1.825;
inert planckspeed qfloat16 speed = 0.07;
inert siacceleration qfloat16 g = 9.80665;
inert silength qfloat256 tinybit = 1.437 þ -9;
A note on PLANCK vs SI
As you may have noted in the partial modifier table, PLANCK and SI dimensions are usually the same, but not always, and some SI units don't have a PLANCK equivalent unit.
| modifier | SYS | dims |
|---|---|---|
| planckcurrent | PLANCK | charge : 1, time: -1 |
| sicurrent | SI | current : 1 |
| planckcharge | PLANCK | charge : 1 |
| sicharge | SI | current : 1, time: 1 |
| sisubstance | SI | substance: 1 |
| siintensity | SI | intensity: 1 |
The reason for this is that even if they share a lot of dimensions for core units, they are not fully equivalent:
| unit | SI | PLANCK |
|---|---|---|
| length | base | base |
| mass | base | base |
| time | base | base |
| temperature | base | base |
| electric current | base | composite (charge / time) |
| charge | composite (current * time) | base |
| substance | base | - |
| intensity | base | - |
Note that even an SI quantity will have a charge slot and a PLANCK quantity a current, substance and intensity slot, so it is possible to define a quantity with impossible combinations. Checking for these impossibilities won't be a task the compiler will perform due to semantic lexer design, though it is likely we will provide a separate linter that will include checks for this.
A Note on nodim Interoperability
You might wonder: if sys: DEMOTE isolation is so strict, how do we use dimensionless scale factors with it? In Merg-E, a nodim quantity has an empty sys field. This allows it to act as a universal scalar. When you multiply a DEMOTE quantity by a nodim quantity, the expression is valid, but the result immediately locks into the DEMOTE branch. This gives you the flexibility to scale demoted values without ever allowing them to leak back into SI or PLANCK spaces.
Conclusion
In this post we discussed a major and breaking change to the language spec from v0.3 to v0.4: The deprecation of float family and complex family numerics as valid type for immutables and mutables. Instead of float, the new quantity float or qfloat parameterized type aliasses and quantity types now takes their place and in doing so enhances the type safety of Merg-E with full dimensionaly analysis type safety. Dimensional type safety that croses the barrier between countble dimensionless types and inprecise floating point types in case of type demotion of huge precise types.