The Polymorphic Domain Specific Language pattern can be used to not only create highly scalable software but languages themselves. Java in particular stands out by its ability to be platform independent. JVM languages actually meet the criteria of being a tool that natively follows the PDSL pattern. This actually is not that surprising a coincidence: the purpose of PDSL to to be able to be highly scalable and highly maintainable despite the complexities of the underlying program.
A general model for PDSL is as follows:
Tier |
Development Tool Example |
Testing Example |
Testing Tool Example |
Artistic Example Domain Specific Language |
JVM Bytecode or Language (Java, Kotlin, etc) |
Gherkin Standard |
12 Tone Scale Specification Language |
.class File |
.feature file |
Java, Python, or other |
Code file |
Vivaldi’s L’estro armonico Interface Definition Language |
Java Virtual Machine Specification |
ANTLR4 |
Webdriver API |
Sheet Music Implementation |
OpenJDK, OracleJDK, JREs, etc |
Java, Python, Golang, TypeScript, etc |
Java, Python, Golang, TypeScript, etc |
The Domain Specific Language (DSL) is the protocol or language used to create artifacts that tell an arbitrary implementer what to do. In the PDSL pattern there is a preference for using DSLs in a declarative way because it provides flexibility for the implementors, but the DSL can still be used imperatively if needed.
Java Bytecode is an example of something that can be used as a Domain Specific Language. Technically Java Virtual Machine (JVM) languages and compiled bytecode are General Purpose Languages, but they can be used to create DSLs (such as a library with a specific, cross platform API).
JVM Bytecode is an abstract instruction set. By itself it doesn’t really provide information on how to do something but is excellent and specifying what to do. This provides Java’s greatest strength of platform independence.
|
Important
|
The ability to create new programming languages that compile to bytecode and are able to run on the many JVM supported platforms highlights one of the strongest advantages of the PDSL design pattern: It can support n frameworks in O(1) time. Ordinarily a new language would have to spend years of resources to gradually support each individual platform, but the abstraction of bytecode allows new JVM languages to immediately leverage these platforms for free! |
The Specification Language (at least in the context of the PDSL pattern) is an artifact produced by the DSL. These are often some sort of text or binary file, but as long as it is something that can be created by the DSL that explains how to do something it will be suitable for specifications.
The .class files produces by the compiled source code of a JVM language would be an example. The class files are essentially just bytecode, but it is bytecode that has been organized to execute some specific (and hopefully useful) program.
Because the .class files are just bytecode they can be interpreted later by Java Runtime Environments on completely different platforms without requiring adjustments to the code.
The purpose of the Interface Definition Language (IDL) is to provide a bridge between our specifications and the (possibly many) clients that will executing it. While our specifications are technically already language agnostic, typically there may be a complex enough need to interpret the specifications that it warrants it’s own tier of architecture. That is to say we need a method of taking the highly abstract DSL and begin transforming it into something concrete that a client can understand.
The IDL may also be used for handling the specification or coordinating or transforming information from the abstract specification into more concrete data that will be needed by lower level implementations.
Ideally the Interface Definition is 100% abstract or as abstract as possible. It should also typically language agnostic. Some use cases may have an IDL that is tightly coupled to a specific programming language or platform, but this should be avoided when possible to allow scaling to other platforms if needed.
The Java Virtual Machine Specification (JVMS) has the job of converting bytecode into machine code, however the JVM specification itself is platform agnostic.
It describes how to do some other necessary work, such as garbage collection, loading class files, security and other infrastructure concerns.
Ultimately if a new platform wants to support Java it can use the JVMS to create a new JVM for itself.
The Clients are actual concrete implementations that interact with the Interface Definition Language. At this point they code can be idiomatic and safely coupled to a specific language or platform. Since the clients follow a common API or contract but vary enough to be useful within different contexts we can say that they are polymorphic at this point.
The Java Runtime Environment (JRE) is what executes bytecode on a specific system. At this point we may have bytecode that means "open a file", but the method of doing that on Mac, Windows or Linux is going to be different. There is a JRE for each of these platforms (as well as many others) that are able to understand how to perform these types of operations on that system.
By having the source code compile to bytecode and interpreted by the JVM we can confidently share code across many platforms without having to recompile it. With a specific JRE for that platform we have a method for executing that code in a way that both makes sense and is understandable by that system.