DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Why "Polyglot Programming" or "Do It Yourself Programming Languages" or "Language Oriented Programming" sucks?
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • Doris vs Elasticsearch: A Comparison and Practical Cost Case Study
  • Finally, an ORM That Matches Modern Architectural Patterns!

Trending

  • Developers Beware: Slopsquatting and Vibe Coding Can Increase Risk of AI-Powered Attacks
  • Proactive Security in Distributed Systems: A Developer’s Approach
  • How To Build Resilient Microservices Using Circuit Breakers and Retries: A Developer’s Guide To Surviving
  • Tired of Spring Overhead? Try Dropwizard for Your Next Java Microservice
  1. DZone
  2. Coding
  3. Languages
  4. A Domain-Specific Language for unit manipulations

A Domain-Specific Language for unit manipulations

By 
Guillaume Laforge user avatar
Guillaume Laforge
·
Mar. 01, 08 · News
Likes (0)
Comment
Save
Tweet
Share
34.7K Views

Join the DZone community and get the full member experience.

Join For Free

Domain-Specific Languages are a hot topic, and have been popularized by languages like Groovy and Ruby thanks to their malleable syntax which make them a great fit for this purpose. In particular, Groovy allows you to create internal DSLs: business languages hosted by Groovy. In a recent research work, Tiago Antão has decided to use Groovy to model the resistance to drugs against the Malaria disease. In two blog posts, Tiago explains some of the tactics he used, and how to put them together to create a mini-language for health related studies. In this work, he needed to represent quantities of medecine, like 300 miligram of Chloroquinine, a drug used against Maralia. Groovy lets you add properties to numbers, and you can represent such quantities with just 300.mg. Inspired by this idea, the purpose of this article is to examine how to build a mini-DSL for manipulating measures and units by leveraging the JScience library.

First of all, let's speak about JScience. JScience is a Java library leveraging generics to represent various measurable quantities. JScience is also the Reference Implementation for JSR-275: javax.measure.*. Whether it is for measuring mass, length, time, amperes or volts (and many more), the calculations you can do are type-safe and checked at compile-time: you cannot add a second to a kilogram, your program wouldn't compile. This is definitely one of the strength of the library. However fluent the library is, the notation used to represent an amount of some unit is still not as readable as scientist could wish.

How do you represent a mass with JScience?

    import static javax.measure.unit.SI.*;
    import javax.measure.*
    import org.jscience.physics.amount.*;
    
    // ...
    
    Amount m3 = Amount.valueOf(3, KILO(GRAM));
    Amount m2 = Amount.valueOf("2 kg");
    Amount sum = m3.plus(m2);

The first expression leverages static imports to represent the KILO (GRAM) unit, while the second simply parses the mass from a String. The last line does just an addition between the two masses. Still, it doesn't look like what a physicist would write. Wouldn't we want to use a mathematical notation, like 3 kg + 2 kg? We will see how you can do this in Groovy.

Our first step will be to add units to numbers. We can't write 2 kg, as it's not valid Groovy, instead, we'll write 2.kg. To so, we'll add some dynamic properties to numbers, thanks to the ExpandoMetaClass mechanism.

    import javax.measure.unit.*
    import org.jscience.physics.amount.*

    // Allow ExpandoMetaClass to traverse class hierarchies
    // That way, properties added to Number will also be available for Integer or BigDecimal, etc.
    ExpandoMetaClass.enableGlobally()

    // transform number properties into an mount of a given unit represented by the property
    Number.metaClass.getProperty = { String symbol -> Amount.valueOf(delegate, Unit.valueOf(symbol)) }
    
    // sample units
    println( 2.kg )
    println( 3.m )
    println( 4.5.in )    

See how we created kilograms, meters and inches? The "metaclass" is what represents the runtime behavior of a class. When assigning a closure to the getProperty property, all the requests for properties on Numbers will be rooted to this closure. This closure then uses the JScience classes to create a Unit and an Amout. The delegate variable that you see in this closure represents the current number on which the properties are accessed.

Okay, fine, but at some point, you'll need to multiply these amounts by some factor, or you will want to add to lengths together. So we'll need to do leverage Groovy's operator overloading to do some arithmetics. Whenever you have methods like multiply(), plus(), minus(), div(), or power(), Groovy will allow you to use the operators *, +, -, /, or **. Some of the conventions for certain of these operations being a bit different from those of Groovy, we have to add some new operator methods for certain of these operations:

    // define opeartor overloading, as JScience doesn't use the same operation names as Groovy
    Amount.metaClass.multiply = { Number factor -> delegate.times(factor) }
    Number.metaClass.multiply = { Amount amount -> amount.times(delegate) }
    Number.metaClass.div = { Amount amount -> amount.inverse().times(delegate) }
    Amount.metaClass.div = { Number factor -> delegate.divide(factor) }
    Amount.metaClass.div = { Amount factor -> delegate.divide(factor) }
    Amount.metaClass.power = { Number factor -> delegate.pow(factor) }
    Amount.metaClass.negative = { -> delegate.opposite() }

    // arithmetics: multiply, divide, addition, substraction, power
    println( 18.4.kg * 2 )
    println( 1800000.kg / 3 )
    println( 1.kg * 2 + 3.kg / 4 )
    println( 3.cm + 12.m * 3 - 1.km )
    println( 1.5.h + 33.s - 12.min )
    println( 30.m**2 - 100.ft**2 )
    
    // opposite and comparison
    println( -3.h )
    println( 3.h < 4.h )

We can also do comparisons, as shown on the last line above, since these types are comparable. Again, free of charge. Something we have covered yet is compound units, such as speed, which is a mix of a distance and a duration. So, if you wanted to use a speed limit, you would like to write 90.km/h but our DSL in its current state would only allow you to write 90.km/1.h, which doesn't really look nice. To circumvent this issue, we could create as many variables as units. We could have a h variable, a km variable, etc. But I'd prefer something more automatic, by letting the script itself provide these units. In Groovy scripts, you can have local variables (whenever you define a variable, it's a local variable), but you can also pass or access variables through a binding. This is a convenient way to pass data around when you integrate Groovy inside a Java application, for instance, to share a certain context of data. We are going to create a new Binding called UnitBinding which will override the getVariable() method, so that all non-local variables which are used withing the Groovy script are looked up in this binding. You'll notice a special treatment for the variable 'out', which is where the println() method looks for for the output stream to use.

    // script binding to transform free standing unit reference like 'm', 'h', etc
    class UnitBinding extends Binding {
        def getVariable(String symbol) {
            if (symbol == 'out') return System.out
            return Amount.valueOf(1, Unit.valueOf(symbol)) 
        }
    }
    
    // use the script binding for retrieving unit references
    binding = new UnitBinding()

    // inverse units
    println( 30.km/h + 2.m/s * 2 )
    println( 3 * 3.mg/L )
    println( 1/2.s - 2.Hz )

The velocity now looks much more like the mathematical notation everybody would use. Now that all this magic is done, there's still one last thing we could do. Sometimes, you may want to convert different units, like feet and meters or inches and centimers. So, as the last step of our units DSL experiments, we'll add a to() method to do convertions.

    // define to() method for unit conversion
    Amount.metaClass.to = { Amount amount -> delegate.to(amount.unit) }
    
    // unit conversion
    println( 200.cm.to(ft) )
    println( 1.in.to(cm) )

At this point, we are able to easily manipulate amounts of any unit in a very convenient and natural notation. The magic trick of adding properties to numbers makes the process of creating a unit DSL straightforward. This DSL is just a small part of the equation, as you may certainly want to represent other business related concepts, but this article will have shown you how to decorate a powerful existing library, so that the code becomes more natural to use by the end users of your DSL. In further articles, we'll discover some other tricks! Stay tuned!

Domain-Specific Language Groovy (programming language)

Opinions expressed by DZone contributors are their own.

Related

  • Why "Polyglot Programming" or "Do It Yourself Programming Languages" or "Language Oriented Programming" sucks?
  • Apache Doris vs Elasticsearch: An In-Depth Comparative Analysis
  • Doris vs Elasticsearch: A Comparison and Practical Cost Case Study
  • Finally, an ORM That Matches Modern Architectural Patterns!

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!