[Nemerle] Семантический контроль над размерностями
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 08:03
Оценка: 160 (12)
Всем доброго времени суток. Некоторое время назад я натолкнулся в этом форуме на код, написанный на шаблонах C++, который позволяет контролировать размерности физических типов во время компиляции: Обеспечение семантического контроля над размерностями станда
Автор: CrystaX
Дата: 21.11.05
. Некоторое другое время назад я начал увлекаться языком Nemerle и мне захотелось решить эту же задачу средствами Nemerle.

Немного о Nemerle (для тех, кто не знает)

Nemerle — это (относительно) новый язык для платформы .NET (под FW 2.0), синтаксис которого похож на C#. Но в нём имеются и некоторые возможности отсутствующие в C# напрочь. А именно:


Как видно, очень многое в язык пришло из мира функционального приграммирования. Последний пункт говорит о том, что на Nemerle мы можем писать довольно мощный метакод (код, создающий другой код). При этом код не выглядит громоздким (и не является таковым) благодаря наличию квази-цитирования.

Больше про Nemerle можно почитать в теме Изучаем макросы Nemerle
Автор: VladD2
Дата: 18.02.06
— там описано, как поставить его себе на машину и использовать + немного написано о самом языке. Я использую для работы набор шаблонов проектов для студии
Автор: Oyster
Дата: 06.03.06
. Также те, комы не лень разгребать километровые посты, могут узнать много полезного о Nemerle в форуме Философия программирования.


Код на Nemerle решает ровно те же задачи, что и код, описанный в Обеспечение семантического контроля над размерностями станда
Автор: CrystaX
Дата: 21.11.05
. А вот и тестовый пример (тоже внаглую украденный оттуда):

using System.Console;
using Oyster.Units;
using Oyster.Units.Macros;

def m1 = Cgs.Mass(1); // 1 грамм
def m2 = Si.Mass(m1); // Автоматическое преобразование в килограммы. В результате получаем 0.001 кг

WriteLine($"Mass in SI: $m2, in CGS: $m1");

def l1 = Cgs.Length(100); // 100 см
def l2 = Si.Length(l1);   // Автоматическое преобразование. Результат - 1 м

WriteLine($"Length in SI: $l2, in CGS: $l1");

def t1 = Cgs.Time(1); // 1 с
def t2 = Si.Time(t1); // Тоже 1 с, т.к. единицы измерения времени в системах СИ и СГС совпадают.

WriteLine($"Time in SI: $t2, in CGS: $t1");

// Теперь немного более сложный пример. Предыдущие величины (масса, длина, время) являются
// базовыми (ортами) в обеих системах единиц. Здесь же мы имеем дело с производными величинами -
// скоростью, енергией и мощностью
def v1 = Cgs.Velocity(10); // 10 см/с
def v2 = Si.Velocity(v1);  // Результат преобразования - 0.1 м/с

WriteLine($"Velocity in SI: $v2, in CGS: $v1");

def e1 = Si.Energy(0.0001); // 10e-4 Дж.
def e2 = Cgs.Energy(e1);    // e2 = 1000 эрг

WriteLine($"Energy in SI: $e1, in CGS: $e2");

// Это - пример работы с внесистемными единицами. В данном случае мощность переводится из ваттов
// в лошадиные силы
def p1 = Si.Power(1000); // 1000 Вт
def p2 = Ext.Power(p1);  // 1.35962 лс

WriteLine($"Power in SI: $p1, in Ext system: $p2");

// Пример работы с простейшими арифметическими действиями
def a1 = Si.Acceleration(1); // 1 м/с2
def a2 = Cgs.Acceleration(a1 + Cgs.Acceleration(200)); // 1 м/с2 + 200 см/с2 = 300 см/с2, как и должно было быть

WriteLine($"Acceleration: a1 = $a1, a2 = $a2");

// Сила
def f1 = Si.Force(10);      // 10 Н
def f2 = Cgs.Force(f1*2);  // 10 Н * 2 = 2000000 дин

def f3 = Si.Force(m1*l2/(t1*t1)); // 1 г * 1 м / (1 с * 1 с) = 0.001 Н
// А вот так уже не скомпилируется - не та размерность
//def f4 = Si.Force(m1*l2/t1);
// Вот что выдаёт ncc:
// Main.n(122,10,122,18): error : each overload has an error during call:
// Main.n(122,10,122,18): error : overload #1, method Oyster.Units.Si.Force..ctor(value : System.Double) : void
// Main.n(122,10,122,18): error : in argument #1 (value) of Oyster.Units.Si.Force, needed a System.Double, got _N_TempUnitType5439: _N_TempUnitType5439 is not a subtype of System.Double [simple require]
// Main.n(122,10,122,18): error : overload #2, method Oyster.Units.Si.Force..ctor(value : Oyster.Units.AbstractUnitType_1_1_-2_0_0_0_0) : void
// Main.n(122,10,122,18): error : in argument #1 (value) of Oyster.Units.Si.Force, needed a Oyster.Units.AbstractUnitType_1_1_-2_0_0_0_0, got _N_TempUnitType5439: _N_TempUnitType5439 is not a subtype of Oyster.Units.AbstractUnitType_1_1_-2_0_0_0_0 [simple require]

WriteLine($"Force: f1 = $f1, f2 = $f2, f3 = $f3");

// Здесь ошибка компиляции - пытаемся складывать енергию, массу и скорость
//WriteLine($"ERROR = $(e1 + e2 + v1 + v2 + m1 + m2)");
// Вот что выдаёт ncc (точнее, мой макрос):
// Main.n(135,52,135,64): error : Expression { e1 + e2 [+] v1 } can't be compiled because units are having different dimensions.

// Здесь происходит преобразование на лету. В результате имеем 0.0002 Дж - результат имеет тот же тип, 
// что и левый операнд
WriteLine($"Result = $(e1 + e2)");

// Здесь выводится единица, т.к. e1 и e2 содержат одно и то же значение, но выраженное в разных 
// системах единиц
WriteLine($"Result = $(e1/e2)")

Исходники в соседних сообщениях. Они разбиты на 2 группы:


Макробиблиотека должна быть скомпилирована в отдельную сборку. Описание физических величин — в другой отдельной сборке. Я использовал для компиляции билд 0.9.2.6168 (т.е. первоапрельский).



Немного оффтопа

Основная цель, которую я преследовал при написании этого кода — изучение возможностей Nemerle (да и самого языка с библиотекой — за время написания кода я узнал много нового для себя). Мне хотелось понять, насколько сложно, например, переписать библиотеку с метакодом, написанную на C++, на Nemerle. А также хотелось показать другим участникам форума, что эта задача вполне разрешима штатными средствами Nemerle.

Этот код не идеален, поскольку я не использую его в реальном приложении. Сейчас я вижу как минимум следующие модификации, которые можно произвести с кодом:


В то же время, в целом я доволен кодом. Мне кажется, что он понятнее, чем тот же код на C++. Да и описание физических величин имхо выглядит удобнее (см. исходники).

Надеюсь, сообщение оказалось полезным. И сорри всем, кто дочитал до этой строчки, за объём текста
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.