JetBrains MPS for those interested # 1
- Tutorial
Introduction
Thank you all for the criticism in the comment under the first post , where I wanted to try to write about MPS, without touching on important topics, so that later I could better start writing in order.
Why do we need Weather?
In the comments to 1 post was the following statement
From this point of view, DSL is like a framework, but with a more convenient interface. Of course, no one will do the framework for one project, except in very monstrous cases. And to make it for a specific subject area - why not? ..
In principle, this is how it works. Good languages are essentially similar to good frameworks: they allow you to write something important without worrying about what we don’t want to write. In the course of the narration, I will periodically turn to other languages for analogies and comparisons.
Syntax
The Weather language, which we want to implement, must fulfill the following task: we must be able to concisely express conditions (weather today, for example) and consequences (weather tomorrow, the day after tomorrow ...).
In the Weather language, we will make our forecasts based on 1 factor: today’s temperature (array of objects time + weather conditions).
const weatherInput = [
{
time: 1501179579253,
temperature: {
unit: "celsius",
value: 23.7
}
},
{
time: 1501185944759,
temperature: {
unit: "fahrenheit",
value: 15.3
}
}
]
I think it will do.
Weather prediction rules for Saint Petersburg
data Today:
[21:23]{
temperature = 23.7 °C
}
[23:06]{
temperature = 15.3 °F
}
We have very simple data - time + temperature in units of measurement. Let's create an abstract concept WeatherTimedData - we need it to store the measurement time and the temperature itself.
Now you need to determine what Temperature and Time are .
Time is implemented very simply - we have time in hours and minutes, but it is displayed as hh : mm
.
If everything is clear with Time , then with Temperature it is a little not. Firstly - value is some kind _FPNumber_String
. This is actually an MPS double, so no big deal. But the question is how to make temperature implementations in different measurement units from the Temperature interface , so that it is also beautiful? And in general, what is a concept interface?
Such concepts cannot be implemented in AST. That is, none at all. Only if another concept expands it, and nothing else. This is done, as in OOP, in order to generalize several classes under one common principle.
This is how I implemented the display in the editor for Temperature :
Here we have the first cell - double value, temperature value, and the second - Read-Only model access . Here we move a little away from practice and move on to theory.
Theory
In MPS, everything is built on concepts, if you draw a direct parallel with OOP, then concepts are classes. They can extend other concepts, implement interfaces, but they can also have some kind of logic. For example, if we describe an abstract class of temperature, then it is necessary to provide for the possibility of setting your own units.
abstract class Temperature{
abstract double value;
public abstract String getUnit();
override String toString(){
return value + this.getUnit();
}
}
One could set unit as a variable and not write an abstract method, but ...
There is an aspect called Behavior . All he can do is add new methods to the concept. That is, we cannot add a variable, therefore we will use the abstract method.
And after that, we can call this method for each implementation of the Temperature concept . But where is it called? How do you code in this MPS? ..
Practice again
We settled on the fact that we have an incomprehensible cell in the Editor aspect - ReadOnly model access. Everything is very simple - if you need to somehow process proeprty / children / reference logically before showing it, and there aren’t enough built-in jokes for this, then we can get the necessary line from the editor’s context and implementation of the concept. If it’s simple, they give us the current object of the concept, that is, implemented, and we can get everything we pushed out of it. In this case, we want to get the unit of measure, so we click on the R / O model access cell and write
By the way, anywhere in the code you can poke a little thing that interests you and press Ctrl + Shift + T and get information about the type of this little thing . For example, if we click on the node in the screenshot above and find out its type, then we will get
node<Название Концепта>
= some kind of concept implementation concept<Название Концепта>
= concept class
So! We already know how to compose temperature by value and unit of measurement, but where do we get which unit of measurement we need? From child implementations, of course.
Create an empty CelsiusTemperature concept, expand Temperature and create behavior for it.
As you can see in the last screenshot, we redefine the getUnit method (it exists in the scope because we inherited the concept from Temperature ) and return "°C"
. Everything is simple!
It remains only to put everything together in WeatherTimedData :
We collect the language and look at the result:
It seems like the truth. Still, of course, there are no weather predictions themselves, there is no backlight, besides, we can have more than 24 hours and less than zero, minutes are also not limited by anything other than the integer dimension ... In the next post, wait for clarifications on a new aspect - constraints and something else- sometime. In the meantime - write a feedback in the comments, everything is as always, if the question is simple - I answer in the same place, if it is extensive and rather as a wish - then I will try to write better with each post. Thanks for attention!
// UPDATE \ I
started a curve repository with a project, where each branch is a new tutorial on Habré. It's him!!!