| --- |
| layout: post |
| status: PUBLISHED |
| published: true |
| title: 'Life on Mars: Units of Measurement systems, Groovy, and domain specific languages |
| (DSLs)' |
| id: 63eb0f83-8162-4390-8662-d7c43d52c7a9 |
| date: '2022-08-13 06:31:47 -0400' |
| categories: groovy |
| tags: |
| - groovy |
| - unitsofmeasurement |
| - domainspecificlanguages |
| - jsr385 |
| permalink: groovy/entry/life-on-mars-units-of |
| --- |
| <p><img src="https://avatars.githubusercontent.com/u/6383598?s=200&v=4" align="right">The Mars Climate Orbiter was launched in 1998 as part of a multi-faceted Mars exploration program. It was lost due to a trajectory calculation error when nearing Mars. An <a href="http://scholar.google.com/scholar?hl=en&q=Stephenson+A%2C+LaPiana+L%2C+Mulville+D%2C+et+al.+Mars+climate+orbiter+mishap+investigation+board+phase+1+report%3B+1999.+https%3A%2F%2Fllis.nasa.gov.+Accessed+October+14%2C+2020." target="_blank">investigation</a> attributed the failure to a measurement mismatch between two software systems: metric units were used by NASA and <a href="https://en.wikipedia.org/wiki/United_States_customary_units" target="_blank">US customary</a> units were used by spacecraft builder Lockheed Martin.</p> |
| <p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/19/Mars_Climate_Orbiter_2.jpg/1280px-Mars_Climate_Orbiter_2.jpg" alt="Mars Climate Orbiter - image credit: wikipedia" style="width:50%"></p> |
| <p>If the system in question had been developed using a units of measurement system, perhaps the failure could have been avoided.</p> |
| <h3>Units of measurement systems</h3> |
| <p>All programming languages have types for representing numbers. As an example, we may have three integers, one representing a height, one a weight and one a temperature. We can write code to add those three integers together, but the result may have no useful meaning. We could start writing our own class hierarchies for each integer type but it can quickly become cumbersome to use the resulting classes. Units of measurement systems attempt to provide a range of commonplace units, ways to construct quantities from those units and ways to manipulate them. Manipulation might involve performing numerical calculation or conversions. Goals of such systems include the ability to provide runtime and/or compile-time type safety. So, we should fail early when trying to perform the earlier mentioned addition of three unrelated quantities.</p> |
| <p>Units of measurement systems aren't new. There are existing systems in F# and Swift and Java has had several versions (and earlier attempts) around standardising such a system including:</p> |
| <ul> |
| <li><a href="https://jcp.org/en/jsr/detail?id=275" target="_blank">JSR 275</a>: Units Specification (rejected earlier attempt)<br></li> |
| <li><a href="https://jcp.org/en/jsr/detail?id=363" target="_blank">JSR 363</a>: Units of Measurement API</li> |
| <li><a href="https://jcp.org/en/jsr/detail?id=385" target="_blank">JSR 385</a>: Units of Measurement API 2.0<br></li> |
| </ul> |
| <p>There are also existing Java libraries like <a href="http://jscience.org/" target="_blank">JScience</a> which have been developed, and <a href="https://objectcomputing.com/resources/publications/sett/june-2006-units-and-measures-with-jscience" target="_blank">shown early promise</a>, but now don't seem to be actively maintained. While the <a href="https://onlinelibrary.wiley.com/doi/10.1002/spe.2926" target="_blank">jury is still out</a> about whether units of measurement systems will spread further into mainstream programming, now seems like a great time to check the status. The JSR 385 maintenance release was approved last year and the latest version of the reference implementation was released earlier this year.</p> |
| <h3>JSR 385: Units of Measurement API 2.0</h3> |
| <p>The first thing we need to do is bring in our dependencies (shown for <a href="https://gradle.org/" target="_blank">Gradle</a> - <a href="https://mvnrepository.com/artifact/tech.units/indriya/2.1.3" target="_blank">other options</a>). The main one is the reference implementation (it brings in the <code>javax.measure</code> API transitively):</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">implementation <span style="color:#6a8759;">'tech.units:indriya:2.1.3'<br></span></pre> |
| <p>JSR 385 is extensible. We'll also bring in some units from the <a href="https://cldr.unicode.org/translation/units" target="_blank">Unicode CLDR</a> units, e.g. <code>MILE</code>:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">implementation <span style="color:#6a8759;">'systems.uom:systems-unicode:2.1'<br></span>implementation <span style="color:#6a8759;">'systems.uom:systems-quantity:2.1'<br></span></pre> |
| <p>Let's follow on with the theme of visiting Mars. We can create variables for the mass and diameter of Mars as follows:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">var </span>mass<span style="font-family:'Courier New',monospace;">ₘ </span>= Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(<span style="color:#6897bb;">6.39E23</span>, <span style="color:#9876aa;font-style:italic;">KILO</span>(<span style="color:#9876aa;font-style:italic;">GRAM</span>))<br><span style="color:#cc7832;">var </span>diameter<span style="font-family:'Courier New',monospace;">ₘ </span>= Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(<span style="color:#6897bb;">6_779</span>, <span style="color:#9876aa;font-style:italic;">KILOMETER</span>)<br>println mass<span style="font-family:'Courier New',monospace;">ₘ<br></span>println diameter<span style="font-family:'Courier New',monospace;">ₘ</span></pre> |
| <p>JSR 385 has metric prefix qualifiers like <code>MICRO</code>, <code>MILLI</code>, <code>CENTI</code>, <code>KILO</code>, <code>MEGA</code>, <code>GIGA</code> and many more. The CLDR units also define some commonly used units like <code>KILOMETER</code>. We could choose either here.</p> |
| <p>When we run this script, it has the following output:</p> |
| <pre>639000000000000000000000 kg
|
| 6779 km
|
| </pre> |
| <p>If we try to compare or add those two values, we'll see an error. Groovy has both static and dynamic natures. Using dynamic code like this:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">println mass<span style="font-family:'Courier New',monospace;">ₘ </span>> diameter<span style="font-family:'Courier New',monospace;">ₘ<br></span></pre> |
| <p>We'll see a runtime error like this:</p> |
| <pre><span style="color:#D03030;">javax.measure.IncommensurableException: km is not compatible with kg</span></pre> |
| <p>Or, with <code>TypeChecked</code> or <code>CompileStatic</code> in play for a statement like this:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">println mass<span style="font-family:'Courier New',monospace;">ₘ</span>.add(diameter<span style="font-family:'Courier New',monospace;">ₘ</span>)</pre> |
| <p>We'd see a compile-time error like this:</p> |
| <pre><span style="color:#d03030">[Static type checking] - Cannot call tech.units.indriya.ComparableQuantity#add(javax.measure.Quantity<javax.measure.quantity.<b>Mass</b>>)
|
| with arguments [tech.units.indriya.ComparableQuantity<javax.measure.quantity.<b>Length</b>>]</span></pre> |
| <p>If for some strange reason we did want to compare or perform calculations between incommensurable types, we can explicitly get the value:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">assert </span>mass<span style="font-family:'Courier New',monospace;">ₘ</span>.<span style="color:#9876aa;">value </span>> diameter<span style="font-family:'Courier New',monospace;">ₘ</span>.<span style="color:#9876aa;">value<br></span></pre> |
| <p>This escape hatch takes off a layer of type safety but requires explicit work to do so. It is normally never the case that you would want to do this.</p> |
| <p>JSR 385 also supports ranges and conversions. We can look at the minimum and maximum temperatures on Mars:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">var </span>minTemp = Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(-<span style="color:#6897bb;">128</span>, <span style="color:#9876aa;font-style:italic;">CELSIUS</span>)<br><span style="color:#cc7832;">var </span>maxTemp = Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(<span style="color:#6897bb;">70</span>, <span style="color:#9876aa;font-style:italic;">FAHRENHEIT</span>)<br>println minTemp<br>println minTemp.to(<span style="color:#9876aa;font-style:italic;">FAHRENHEIT</span>)<br>println maxTemp<br>println maxTemp.to(<span style="color:#9876aa;font-style:italic;">CELSIUS</span>)<br>println QuantityRange.<span style="color:#9876aa;font-style:italic;">of</span>(minTemp, maxTemp)<br></pre> |
| <p>It's quite a bit colder than Earth! When run, this script has the following output:</p> |
| <pre>-128 ℃
|
| -198.400 ((K*5)/9)+459.67
|
| 70 ((K*5)/9)+459.67
|
| 21.1111111111111111111111111111111 ℃
|
| min= -128 ℃, max= 70 ((K*5)/9)+459.67</pre> |
| <p>In case you're wondering about the strange looking unit display for Fahrenheit temperatures, the definition of that unit that we are using, defines degrees Fahrenheit using a formula calculated from the temperature in degrees Kelvin.</p> |
| <p>We'd see the same thing if using the <code>MILE</code> unit:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">println diameter<span style="font-family:'Courier New',monospace;">ₘ</span>.to(<span style="color:#9876aa;font-style:italic;">MILE</span>)<br></pre> |
| <p>Which shows us that the diameter of Mars is a little over 4200 miles:</p> |
| <pre>4212.275312176886980036586335798934 (m*1609344)/1000</pre> |
| <h3>Adding some metaprogramming</h3> |
| <p>Groovy has various features which allow methods to be (apparently) added to classes. We'll use extension methods. This technique involves writing static methods in a helper class using certain conventions. The first parameter in all such methods is the target of the extension. Groovy code referencing instances of the target class have code that can call such a method as if it existed on the target class. In reality, the Groovy compiler or runtime funnels the call through the helper class. For us, it means we will have methods like <code>getMeters()</code> on the <code>Number</code> class which using Groovy's shorthand's for property notation allows for very compact quantity definitions like <code>5.meters</code>. We'll also add some methods to allow Groovy's normal operator overloading syntax to apply:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">class </span>UomExtensions {<br> <span style="color:#cc7832;">static </span>Quantity<Length> getCentimeters(Number num) { Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(num, <span style="color:#9876aa;font-style:italic;">CENTI</span>(<span style="color:#9876aa;font-style:italic;">METRE</span>)) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Length> getMeters(Number num) { Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(num, <span style="color:#9876aa;font-style:italic;">METRE</span>) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Length> getKilometers(Number num) { Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(num, <span style="color:#9876aa;font-style:italic;">KILO</span>(<span style="color:#9876aa;font-style:italic;">METRE</span>)) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Length> getCm(Number num) { <span style="color:#9876aa;font-style:italic;">getCentimeters</span>(num) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Length> getM(Number num) { <span style="color:#9876aa;font-style:italic;">getMeters</span>(num) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Length> getKm(Number num) { <span style="color:#9876aa;font-style:italic;">getKilometers</span>(num) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Mass> getKilograms(Number num) { Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(num, <span style="color:#9876aa;font-style:italic;">KILO</span>(<span style="color:#9876aa;font-style:italic;">GRAM</span>)) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Mass> getKgs(Number num) { <span style="color:#9876aa;font-style:italic;">getKilograms</span>(num) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Time> getHours(Number num) { Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(num, <span style="color:#9876aa;font-style:italic;">HOUR</span>) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Time> getSeconds(Number num) { Quantities.<span style="color:#9876aa;font-style:italic;">getQuantity</span>(num, <span style="color:#9876aa;font-style:italic;">SECOND</span>) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Time> getHr(Number num) { <span style="color:#9876aa;font-style:italic;">getHours</span>(num) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Time> getS(Number num) { <span style="color:#9876aa;font-style:italic;">getSeconds</span>(num) }<br><br> <span style="color:#cc7832;">static </span>Quantity<Speed> div(Quantity<Length> q, Quantity<Time> divisor) { q.divide(divisor) <span style="color:#cc7832;">as </span>Quantity<Speed> }<br><br> <span style="color:#cc7832;">static </span><<span style="color:#507874;">Q</span>> Quantity<<span style="color:#507874;">Q</span>> div(Quantity<<span style="color:#507874;">Q</span>> q, Number divisor) { q.divide(divisor) }<br><br> <span style="color:#cc7832;">static </span><<span style="color:#507874;">Q</span>> Quantity<<span style="color:#507874;">Q</span>> plus(Quantity<<span style="color:#507874;">Q</span>> q, Quantity<<span style="color:#507874;">Q</span>> divisor) { q.add(divisor) }<br><br> <span style="color:#cc7832;">static </span><<span style="color:#507874;">Q</span>> Quantity<<span style="color:#507874;">Q</span>> minus(Quantity<<span style="color:#507874;">Q</span>> q, Quantity<<span style="color:#507874;">Q</span>> divisor) { q.subtract(divisor) }<br>}</pre> |
| <p>Note that we have longer and shorter versions of many of the methods, e.g. <code>kg</code> and <code>kilogram</code>, <code>m</code> and <code>meter</code>. We didn't need a method for <code>multiply</code> since it is already using the name Groovy expects.<br></p> |
| <p>Now we can write very short definitions to declare or compare times and lengths:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">def </span>s = <span style="color:#6897bb;">1</span>.<span style="color:#9876aa;">s<br></span><span style="color:#cc7832;">assert </span><span style="color:#6897bb;">1000</span>.<span style="color:#9876aa;">meters </span>== <span style="color:#6897bb;">1</span>.<span style="color:#9876aa;">km </span>&& <span style="color:#6897bb;">1</span>.<span style="color:#9876aa;">m </span>== <span style="color:#6897bb;">100</span>.<span style="color:#9876aa;">cm</span></pre> |
| <p>We can also declare variables for acceleration due to gravity on Earth and Mars. Gravity is a lot less on Mars:<br></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">var </span>g<span style="font-family:'Courier New',monospace;">ₘ </span>= <span style="color:#6897bb;">3.7</span>.<span style="color:#9876aa;">m</span>/s/s<br><span style="color:#cc7832;">var </span>g<span style="font-family:'Courier New',monospace;">ₑ </span>= <span style="color:#6897bb;">9.8</span>.<span style="color:#9876aa;">m</span>/s/s<br><span style="color:#cc7832;">assert </span>g<span style="font-family:'Courier New',monospace;">ₑ</span>.toString() == <span style="color:#6a8759;">'9.8 m/s²'<br></span><span style="color:#cc7832;">assert </span>g<span style="font-family:'Courier New',monospace;">ₑ </span>> g<span style="font-family:'Courier New',monospace;">ₘ</span></pre> |
| <p>We can also use the operator overloading in calculations (here showing that the Earth has a diameter that is between 1.8 and 2 times bigger than that of Mars):</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">var </span>diameter<span style="font-family:'Courier New',monospace;">ₑ </span>= <span style="color:#6897bb;">12_742</span>.<span style="color:#9876aa;">kilometers<br></span><span style="color:#cc7832;">assert </span>diameter<span style="font-family:'Courier New',monospace;">ₘ </span>+ diameter<span style="font-family:'Courier New',monospace;">ₘ </span>> diameter<span style="font-family:'Courier New',monospace;">ₑ<br></span><span style="color:#cc7832;">assert </span>diameter<span style="font-family:'Courier New',monospace;">ₑ </span>- diameter<span style="font-family:'Courier New',monospace;">ₘ </span>< diameter<span style="font-family:'Courier New',monospace;">ₘ<br></span><span style="color:#cc7832;">assert </span>diameter<span style="font-family:'Courier New',monospace;">ₘ </span>* <span style="color:#6897bb;">1.8 </span>< diameter<span style="font-family:'Courier New',monospace;">ₑ</span></pre> |
| <p>Even though we have more compact expressions, the same data types are in play that we saw previously. They're just a little nicer to type.</p> |
| <h3>A dynamic DSL for controlling a Mars Rover</h3> |
| <p>Let's now look at how you could write a little Domain-Specific-Language (DSL) to control a Mars rover robot.</p> |
| <p><img src="https://upload.wikimedia.org/wikipedia/commons/f/f3/Curiosity_Self-Portrait_at_%27Big_Sky%27_Drilling_Site.jpg" alt="Mars Rover Selfie - image credit: wikipedia" style="width:40%;"></p> |
| <p>First, we'll write a <code>Direction</code> enum as part of our robot domain model:<br></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">enum </span>Direction {<br> <span style="color:#9876aa;font-style:italic;">left</span>, <span style="color:#9876aa;font-style:italic;">right</span>, <span style="color:#9876aa;font-style:italic;">forward</span>, <span style="color:#9876aa;font-style:italic;">backward<br></span>}<br></pre> |
| <p>There are many ways to write DSLs in Groovy. We'll use a little trick where the verbs are represented as keys in a map. Our DSL then looks like this:<br></p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">def </span>move(Direction dir) {<br> [<span style="color:#6a8759;">by</span>: <span style="font-weight:bold;">{ </span>Quantity<Length> dist <span style="font-weight:bold;">-><br></span><span style="font-weight:bold;"> </span>[<span style="color:#6a8759;">at</span>: <span style="font-weight:bold;">{ </span>Quantity<Speed> speed <span style="font-weight:bold;">-><br></span><span style="font-weight:bold;"> </span>println <span style="color:#6a8759;">"robot moved </span>$dir<span style="color:#6a8759;"> by </span>$dist<span style="color:#6a8759;"> at </span>$speed<span style="color:#6a8759;">"<br></span><span style="color:#6a8759;"> </span><span style="font-weight:bold;">}</span>]<br> <span style="font-weight:bold;">}</span>]<br>}<br></pre> |
| <p>Here the implementation is just going to print out a message indicating all of the values it is processing. The real robot would send signals to the rover's robotic subsystems.</p> |
| <p>Our script for controlling the rover now looks like this:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">move <span style="color:#9876aa;font-style:italic;">right </span>by <span style="color:#6897bb;">2</span>.<span style="color:#9876aa;">m </span>at <span style="color:#6897bb;">5</span>.<span style="color:#9876aa;">cm</span>/s<br></pre> |
| <p>Which when run gives this output:</p> |
| <pre>robot moved right by 2 m at 5 cm/s</pre> |
| <p>As we saw earlier, this is backed by our JSR 385 types. We'll certainly get fail-early runtime errors if there are any calculations involving mismatched types.</p> |
| <p>If we enable static typing, some additional errors will be detected at compile but because of the very dynamic style of our DSL implementation, not all runtime errors are reflected by typing information. If we want, we can change our DSL implementation to use richer types and that will support better static typing checking. We'll look at one way to do that next.</p> |
| <h3>A type-rich DSL for the Rover</h3> |
| <p>Now, instead of using our nested map style we saw previously, we create several richly-typed helper classes and define our <code>move</code> method in terms of those classes:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#cc7832;">class </span>MoveHolder {<br> Direction <span style="color:#9876aa;">dir<br></span><span style="color:#9876aa;"> </span>ByHolder by(Quantity<Length> dist) {<br> <span style="color:#cc7832;">new </span>ByHolder(<span style="color:#6a8759;">dist</span>: dist, <span style="color:#6a8759;">dir</span>: <span style="color:#9876aa;">dir</span>)<br> }<br>}<br><br><span style="color:#cc7832;">class </span>ByHolder {<br> Quantity<Length> <span style="color:#9876aa;">dist<br></span><span style="color:#9876aa;"> </span>Direction <span style="color:#9876aa;">dir<br></span><span style="color:#9876aa;"> </span><span style="color:#cc7832;">void </span>at(Quantity<Speed> speed) {<br> println <span style="color:#6a8759;">"robot moved </span>$<span style="color:#9876aa;">dir</span><span style="color:#6a8759;"> by </span>$<span style="color:#9876aa;">dist</span><span style="color:#6a8759;"> at </span>$speed<span style="color:#6a8759;">"<br></span><span style="color:#6a8759;"> </span>}<br>}<br><br><span style="color:#cc7832;">static </span>MoveHolder move(Direction dir) {<br> <span style="color:#cc7832;">new </span>MoveHolder(<span style="color:#6a8759;">dir</span>: dir)<br>}<br></pre> |
| <p>While our DSL implementation has changed, the robot scripts remain the same:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#9876aa;font-style:italic;">move right </span>by <span style="color:#6897bb;">2</span>.<span style="color:#9876aa;">m </span>at <span style="color:#6897bb;">5</span>.<span style="color:#9876aa;">cm</span>/s</pre> |
| <p>Indeed, if we use Groovy dynamic nature, we can still run the same script and will notice no change.</p> |
| <p>If however, we enable static checking and have a script with an error like this:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#9876aa;font-style:italic;">move forward </span>by <span style="color:#6897bb;">2</span>.<span style="color:#9876aa;">kgs</span><br></pre> |
| <p>We'll now see a compile-time error:</p> |
| <pre><span style="color:#C03030;">[Static type checking] - Cannot call MoveHolder#by(javax.measure.Quantity<javax.measure.quantity.<b>Length</b>>) with arguments [javax.measure.Quantity<javax.measure.quantity.<b>Mass</b>>]
|
| </span></pre> |
| <p>It is great to get this additional earlier feedback on script errors, so you may wonder why we don't write our DSL implementations like this all of the time? Actually, both the dynamic and static flavors of our DSL can be useful at different times. When prototyping our script DSL, deciding on all the nouns and verbs that we should be using to control our robot, the dynamic flavored style can be much quicker to write especially during early iterations which might evolve and change rapidly. Once the DSL language has been locked down, we can invest in adding the richer types. In the rover scenario, it might also be the case that the rover itself has limited power and so may not want to perform additional type checking steps. We might run all scripts through a type checker back at mission control before sending them through to the rover where they may be enacted in dynamic mode.</p> |
| <h3>Adding custom type checking</h3> |
| <p>There is one additional language feature of Groovy we haven't mentioned. Groovy's type checking mechanism is extensible, so we'll have a look at using that feature here. The rover's speed is <a href="https://mars.nasa.gov/mars2020/spacecraft/rover/wheels/" target="_blank">rather limited</a>, "<i>In the case of exploring Mars, however, speed isn't the most relevant quality. It's about the journey and the destinations along the way. The slow pace is energy-efficient...</i>". Let's look at limiting the speed to avoid unsafe or energy wasting movement.</p> |
| <p>We could put early defensive checks in our DSL implementation to detect undesirable manoeuvres but we can also use type checking extensions for certain kinds of errors. Groovy in fact has its own DSL for writing such extensions. That's a topic for its own blog but here's what the code looks like:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;">afterMethodCall { call -><br> <span style="color:#cc7832;">def </span>method = getTargetMethod(call)<br> <span style="color:#cc7832;">if </span>(method.name != <span style="color:#6a8759;">'at'</span>) <span style="color:#cc7832;">return<br></span><span style="color:#cc7832;"> if </span>(call.arguments.size() != <span style="color:#6897bb;">1</span>) <span style="color:#cc7832;">return<br></span><span style="color:#cc7832;"> def </span>arg = call.arguments[<span style="color:#6897bb;">0</span>]<br> <span style="color:#cc7832;">if </span>(arg !instanceof BinaryExpression) <span style="color:#cc7832;">return<br></span><span style="color:#cc7832;"> def </span>left = arg.leftExpression<br> <span style="color:#cc7832;">if </span>(left !instanceof PropertyExpression) <span style="color:#cc7832;">return<br></span><span style="color:#cc7832;"> def </span>obj = left.objectExpression<br> <span style="color:#cc7832;">if </span>(obj !instanceof ConstantExpression) <span style="color:#cc7832;">return<br></span><span style="color:#cc7832;"> if </span>(obj.value > <span style="color:#6897bb;">5</span>) {<br> addStaticTypeError(<span style="color:#6a8759;">"Speed of </span>$obj.value<span style="color:#6a8759;"> is too fast!"</span>,call)<br> handled = <span style="color:#cc7832;">true<br></span><span style="color:#cc7832;"> </span>}<br>}<br></pre> |
| <p>This is only a partial implementation, it's make numerous assumptions. We could remove those assumptions by adding more code but for now we'll keep this simplified version.</p> |
| <p>So, now the following script (with the above type checking extension applied) compiles fine:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#9876aa;font-style:italic;">move right </span>by <span style="color:#6897bb;">2</span>.<span style="color:#9876aa;">m </span>at <span style="color:#6897bb;">5</span>.<span style="color:#9876aa;">cm</span>/s</pre> |
| <p>But this script fails:</p> |
| <pre style="background-color:#2b2b2b;color:#a9b7c6;font-family:'JetBrains Mono',monospace;font-size:9.6pt;"><span style="color:#9876aa;font-style:italic;">move right </span>by <span style="color:#6897bb;">2</span>.<span style="color:#9876aa;">m </span>at <span style="color:#6897bb;">6</span>.<span style="color:#9876aa;">cm</span>/s</pre> |
| <p>The error message is:</p> |
| <pre><span style="color:#C03030;">[Static type checking] - Speed of 6 is too fast!</span></pre> |
| <h3>Further information</h3> |
| <ul> |
| <li><a href="https://www.jcp.org/en/jsr/detail?id=385" target="_blank">JSR 385: Units of Measurement API 2.0</a></li> |
| <li><a href="https://unitsofmeasurement.github.io/2017/taste_of_indriya.html" target="_blank">A Taste of Indriya</a><br></li> |
| <li><a href="https://betterprogramming.pub/unit-and-measurement-in-swift-7c6be4a25586" target="_blank">Unit and Measurement in Swift</a></li> |
| <li><a href="https://docs.microsoft.com/en-us/archive/blogs/andrewkennedy/" target="_blank">Units of Measure in F#</a><br></li> |
| <li><a href="https://varkor.github.io/blog/2018/07/30/types-for-units-of-measure.html" target="_blank">Types for units of measure</a></li> |
| <li><a href="https://www.slideshare.net/keilw/how-jsr-385-could-have-saved-the-mars-climate-orbiter-jfokus-2020" target="_blank">How JSR 385 could have Saved the Mars Climate Orbiter - JFokus 2020</a> (slide deck)</li> |
| <li><a href="https://www.manning.com/books/groovy-in-action-second-edition" target="_blank">Groovy in Action</a> (in particular the DSL chapter)</li> |
| <li><a href="https://objectcomputing.com/resources/publications/sett/june-2006-units-and-measures-with-jscience" target="_blank">Units and Measures with JScience</a></li> |
| <li><a href="https://onlinelibrary.wiley.com/doi/10.1002/spe.2926" target="_blank">Unit of measurement libraries, their popularity and suitability</a></li> |
| <li><a href="https://speakerdeck.com/glaforge/groovy-domain-specific-languages-techniques" target="_blank">Groovy Domain-Specific Languages techniques</a> (slide deck)</li> |
| <li><a href="http://docs.groovy-lang.org/docs/latest/html/documentation/core-domain-specific-languages.html" target="_blank">Domain-Specific Languages</a> (Groovy documentation)</li> |
| <li><a href="https://www.baeldung.com/javax-measure" target="_blank">Introduction to javax.measure</a></li> |
| <li><a href="https://github.com/paulk-asert/UomGroovy" target="_blank">https://github.com/paulk-asert/UomGroovy</a> (repo)<br></li> |
| </ul> |
| <h3>Conclusion</h3> |
| <p>We have looked at using the JSR 385 <code>javax.measure</code> API using Groovy and added some DSL examples to make using the API a little nicer.</p> |