Всем доброго времени суток. Некоторое время назад я натолкнулся в этом форуме на код, написанный на шаблонах C++, который позволяет контролировать размерности физических типов во время компиляции: Обеспечение семантического контроля над размерностями станда
. Некоторое другое время назад я начал увлекаться языком Nemerle и мне захотелось решить эту же задачу средствами Nemerle.
Немного о Nemerle (для тех, кто не знает)
Nemerle — это (относительно) новый язык для платформы .NET (под FW 2.0), синтаксис которого похож на C#. Но в нём имеются и некоторые возможности отсутствующие в C# напрочь. А именно:
Удобная работа с лямбдами и туплами
Сопоставление по образцу
Автоматический вывод типов (т.е. язык позволяет очень редко указывать тип выражений явно, при этом статическая типизация сохраняется)
Синтаксические макросы (убедительная просьба не путать с макросами Си)
Как видно, очень многое в язык пришло из мира функционального приграммирования. Последний пункт говорит о том, что на Nemerle мы можем писать довольно мощный метакод (код, создающий другой код). При этом код не выглядит громоздким (и не является таковым) благодаря наличию квази-цитирования.
— там описано, как поставить его себе на машину и использовать + немного написано о самом языке. Я использую для работы набор шаблонов проектов для студии
. А вот и тестовый пример (тоже внаглую украденный оттуда):
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 группы:
Исходники макробиблиотеки
Описание физических величин на простеньком DSL-е
Макробиблиотека должна быть скомпилирована в отдельную сборку. Описание физических величин — в другой отдельной сборке. Я использовал для компиляции билд 0.9.2.6168 (т.е. первоапрельский).
Немного оффтопа
Основная цель, которую я преследовал при написании этого кода — изучение возможностей Nemerle (да и самого языка с библиотекой — за время написания кода я узнал много нового для себя). Мне хотелось понять, насколько сложно, например, переписать библиотеку с метакодом, написанную на C++, на Nemerle. А также хотелось показать другим участникам форума, что эта задача вполне разрешима штатными средствами Nemerle.
Этот код не идеален, поскольку я не использую его в реальном приложении. Сейчас я вижу как минимум следующие модификации, которые можно произвести с кодом:
Рефакторинг
Переписывание кода на генерацую value-types — это может ускорить код по понятным причинам. Для этого можно использовать интерфейсы и немного покрутить с генериками
Добавление большего количества метаинформации к генерируемым классам для формирования более информативных сообщений об ошибках
Переписывание макрооператоров. Сейчас, например, при использовании макрооператоров из пространства имён Oyster.Units.Macros невозможно пользоваться т.н. каррингом в стиле Nemerle
, т.е. выражениями вида _ + _
Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того)
В то же время, в целом я доволен кодом. Мне кажется, что он понятнее, чем тот же код на C++. Да и описание физических величин имхо выглядит удобнее (см. исходники).
Надеюсь, сообщение оказалось полезным. И сорри всем, кто дочитал до этой строчки, за объём текста
using System;
using Nemerle.Utility;
namespace Oyster.Units
{
/// Automatically attached to all concrete unit type implemetationspublic class ConcreteUnitTypeAttribute : Attribute
{
[Accessor] _normalizationCoef : double;
public this(normalizationCoef : double)
{
_normalizationCoef = normalizationCoef
}
}
}
UnitTypeBase.n:
using Nemerle.Utility;
namespace Oyster.Units
{
/// Base class for all unit typesabstract public class UnitTypeBase
{
/// Unit value as double
[Accessor] _value : double;
/// Constructs given unit type instance from any other unit type instanceprotected this(value : UnitTypeBase)
{
// Perform normalization-denormalization herethis(value._value * value.NormalizationCoef / NormalizationCoef)
}
/// Constructs given unit type instance from doubleprotected this(value : double)
{
_value = value
}
/// Unit type normalization coefficientabstract public NormalizationCoef : double { get; }
/// ToString() overrideoverride public ToString() : string
{
_value.ToString()
}
}
}
CompileTimeUtil.n:
using System;
using System.Collections.Generic;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Utility;
using NST = Nemerle.Compiler.NamespaceTree;
namespace Oyster.Units.Macros
{
/// Used at compile time by Units macros - stores some intermediate informationmodule CompileTimeUtil
{
/// Units NS namepublic UnitsNamespace = "Oyster.Units";
/// Stores list of base units (such as mass or length) registered in the systempublic mutable BaseUnits : list[string];
/// Stores list of all registered units including aliasespublic mutable AllUnits : list[string];
/// Stores powers list for given base unit or aliaspublic mutable PowersMap : Dictionary[string, list[int]] = Dictionary();
/// Generates (or gets) given unit type with given or automatically generated name,
/// given base units powers and given normalization coefficientpublic GetUnitType(env : GlobalEnv, typeName : string, baseUnitPowers : list[int], normalizationCoef : double) : TypeInfo
{
// Determine name of the type to be generated
def typeName = match (typeName)
{
| null => Macros.NewSymbol("TempUnitType")
| _ => Macros.UseSiteSymbol(typeName)
};
// Try to find this type by name among existing ones
def unitType = env.LookupType([ typeName.Id ]);
if (unitType.HasValue)
{
unitType.Value
}
else
{
// Generate new type
def abstractTypeName = Macros.UseSiteSymbol(GetAbstractUnitType(baseUnitPowers).Name);
def unitType = DefineInCustomNamespace(<[ decl:
[ConcreteUnitType($(normalizationCoef : double))]
public class $(typeName : name) : $(abstractTypeName : name)
{
public this(value : $(abstractTypeName : name))
{
base(value)
}
public this(value : double)
{
base(value)
}
override public NormalizationCoef : double
{
get { $(normalizationCoef : double) }
}
static public @:>(value : $(typeName : name)) : double
{
value.Value
}
static public @:(value : double) : $(typeName : name)
{
$(typeName : name)(value)
}
static public @*(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value * value)
}
static public @/(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value / value)
}
static public @+(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value + value)
}
static public @-(unit : $(typeName : name), value : double) : $(typeName : name)
{
$(typeName : name)(unit.Value - value)
}
static public @*(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value * unit.Value)
}
static public @/(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value / unit.Value)
}
static public @+(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value + unit.Value)
}
static public @-(value : double, unit : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value - unit.Value)
}
static public @++(value : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value.Value + 1)
}
static public @--(value : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(value.Value - 1)
}
static public @+(value : $(typeName : name)) : $(typeName : name)
{
value
}
static public @-(value : $(typeName : name)) : $(typeName : name)
{
$(typeName : name)(0 - value.Value)
}
}
]>);
unitType.Compile();
unitType
}
}
/// Generates (or gets) abstract unit type for given base unit powers
GetAbstractUnitType(baseUnitPowers : list[int]) : TypeInfo
{
def env = GlobalEnv.Core;
// Try to find given type
def abstractTypeName = $"$UnitsNamespace.AbstractUnitType$(GenerateUnitTypeSuffix(baseUnitPowers))";
def abstractType = env.LookupType([ abstractTypeName ]);
if (abstractType.HasValue)
{
abstractType.Value
}
else
{
// Generate abstract type
def abstractType = DefineInCustomNamespace(<[ decl:
abstract public class $(abstractTypeName : usesite) : UnitTypeBase
{
protected this(value : $(abstractTypeName : usesite))
{
base(value)
}
protected this(value : double)
{
base(value)
}
}
]>);
abstractType.Compile();
abstractType
}
}
/// Defines given class in custom global namespacepublic DefineInCustomNamespace(decl : ClassMember.TypeDeclaration) : TypeBuilder
{
def builder = GlobalEnv.Core.Define(decl);
// Register newly created type manually - otherwise it will be unbound
NST.ExactPath(NString.Split(builder.Name, '.')).Value = NST.TypeInfoCache.Cached(builder);
builder
}
/// Increases power for given unit into listpublic ModifyBaseUnitPower(baseUnitPowers : list[int], unitName : string, f : int -> int) : list[int]
{
// Maybe prepare list full of zeroes
def baseUnitPowers =
if (baseUnitPowers.Length != 0) baseUnitPowers else List.Map(BaseUnits, fun(_) { 0 });
List.Map2(BaseUnits, baseUnitPowers, fun(n, i) { if (n == unitName) f(i) else i })
}
/// Generates abstract unit type suffix basing on base unit powerspublic GenerateUnitTypeSuffix(baseUnitPowers : list[int]) : string
{
| [] => string.Empty
| _ => "_" + baseUnitPowers.Head.ToString() + GenerateUnitTypeSuffix(baseUnitPowers.Tail)
}
/// Parses abstract unit type suffixpublic ParseUnitTypeSuffix(typeName : string) : list[int]
{
List.Map(NString.Split(typeName.Substring(typeName.IndexOf('_') + 1), '_'), int.Parse)
}
/// Returns PExpr typepublic GetExprType(ctx : Typer, expr : PExpr) : TypeInfo
{
ctx.TypeExpr(expr).ty.FixedValue.TypeInfo
}
/// Returns normalization coef if type is concrete unit typepublic GetUnitTypeCoef(t : TypeInfo) : option[double]
{
| _ is TypeBuilder =>
// Get attribute using Nemerle approach
mutable value = None();
foreach (attr in t.GetModifiers().GetCustomAttributes())
{
| <[ ConcreteUnitType($(coef : double)) ]> => value = Some(coef)
};
value
| _ =>
// "Usual" attribute gathering
def attr = Attribute.GetCustomAttribute(t.SystemType, typeof(ConcreteUnitTypeAttribute));
def attr = attr :> ConcreteUnitTypeAttribute;
if (attr == null) None() else Some(attr.NormalizationCoef)
}
/// Used inside ariphmetic op macrospublic OpMacroBody(
ctx : Typer,
lexpr : PExpr,
rexpr : PExpr,
opName : string,
opExpr : PExpr,
someFun : TypeInfo * TypeInfo * double * double -> PExpr) : PExpr
{
def ltype = GetExprType(ctx, lexpr);
def rtype = GetExprType(ctx, rexpr);
match ((GetUnitTypeCoef(ltype), GetUnitTypeCoef(rtype)))
{
| (Some(lcoef), Some(rcoef)) =>
someFun(ltype, rtype, lcoef, rcoef)
| _ =>
def macroPath = NST.ExactPath(["Oyster", "Units", "Macros", opName]);
def value = macroPath.Value;
macroPath.Value = NST.TypeInfoCache.No();
def resexpr = ctx.TypeExpr(opExpr);
macroPath.Value = value;
<[ $(resexpr : typed) ]>
}
}
/// Used inside * and / macrospublic MulDivMacroBody(
ctx : Typer,
lexpr : PExpr,
rexpr : PExpr,
opName : string,
opExpr : PExpr,
powersFun : int * int -> int,
coefFun : double * double -> double,
coefOpExpr : PExpr) : PExpr
{
OpMacroBody(
ctx,
lexpr,
rexpr,
opName,
opExpr,
fun (ltype, rtype, lcoef, rcoef)
{
def powers = List.Map2(
ParseUnitTypeSuffix(ltype.BaseType.Name),
ParseUnitTypeSuffix(rtype.BaseType.Name),
powersFun);
def concreteType = GetUnitType(ctx.Env, null, powers, 1.0);
<[ $(concreteType.FullName : usesite)(double.op_Multiply($coefOpExpr, $(coefFun(lcoef, rcoef) : double))) ]>
})
}
/// Used inside + and - macrospublic AddSubMacroBody(
ctx : Typer,
lexpr : PExpr,
rexpr : PExpr,
opName : string,
opExpr : PExpr,
ctorFun : PExpr -> PExpr) : PExpr
{
OpMacroBody(
ctx,
lexpr,
rexpr,
opName,
opExpr,
fun (ltype, rtype, lcoef, rcoef)
{
match (ltype.BaseType.Name)
{
| s when s == rtype.BaseType.Name =>
def coef = rcoef / lcoef;
def paramExpr = ctorFun(<[ double.op_Multiply($rexpr.Value, $(coef : double)) ]>);
<[ $(ltype.FullName : usesite)($paramExpr) ]>
| _ =>
Message.FatalError(
$"Expression { $lexpr [$opName] $rexpr } can't be compiled "
"because units are having different dimensions.")
}
})
}
}
}
UnitTypesMacro.n:
using System;
using System.Collections.Generic;
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Macros;
using System.Console;
namespace Oyster.Units.Macros
{
using CT = CompileTimeUtil;
/// Declares unit types with convertion rules
macro unittypes(namespaceName, body)
syntax ("unittypes", namespaceName, body)
{
// Parse namespace name
def namespaceName = match (namespaceName)
{
| <[ $(n : name) ]> => n.Id
| _ => Message.FatalError($"Invalid unittypes syntax: expected namespace name part, got $namespaceName")
};
// Prepare normalization coefficients map
def coefSubMap = Dictionary();
// Parse units
match (body)
{
| <[ { .. $unitDecls } ]> =>
// Check if BaseUnits must be initialized
def initBaseUnits = match (CT.BaseUnits)
{
| null =>
CT.BaseUnits = [];
true
| _ => false
};
mutable units = [];
// Process all phys types and add info into PhysTypeHelper
mutable index = 0;
mutable calcBaseUnitPowers = initBaseUnits;
foreach (<[ $decl ]> in unitDecls)
{
def unitCoef = match (decl)
{
| <[ $(n : name) ]> => (n.Id, 1.0)
| <[ $(n : name) : ($coefExpr) ]> =>
// Function parses simple coefficient exprs
def parseCoefExpr(expr)
{
| <[ $(d : double) ]> => d
| <[ $lhs * $(d : double) ]> => parseCoefExpr(lhs) * d
| <[ $lhs / $(d : double) ]> => parseCoefExpr(lhs) / d
| _ => Message.FatalError($"Invalid unittypes syntax: expected \"UnitName [: (coefficient expr)]\", got $decl")
};
(n.Id, parseCoefExpr(coefExpr))
| <[ $(n : name) = $aliasExpr ]> =>
// Aliases are started - remember it
when (calcBaseUnitPowers)
{
// Fill base units list
CT.BaseUnits = units.Reverse();
// Prepare powers lists
CT.BaseUnits.Iter(fun(unitName) { CT.PowersMap[unitName] = CT.ModifyBaseUnitPower([], unitName, _ + 1) });
calcBaseUnitPowers = false
};
// Prepare powers list
def oldN = n.Id == "SpecificGravity";
def parseAliasExpr(expr)
{
| <[ $(n : name) ^ $(x : int) ]> => List.Map(CT.PowersMap[n.Id], _ * x)
| <[ $lhs * $(n : name) ^ $(x : int) ]> =>
List.Map2(parseAliasExpr(lhs), parseAliasExpr(<[ $(n : name) ^ $(x : int) ]>), _ + _)
| <[ $lhs / $(n : name) ^ $(x : int) ]> =>
List.Map2(parseAliasExpr(lhs), parseAliasExpr(<[ $(n : name) ^ $(x : int) ]>), _ - _)
| <[ $(n : name) ]> => parseAliasExpr(<[ $(n : name) ^ 1 ]>)
| <[ $lhs * $(n : name) ]> => parseAliasExpr(<[ $lhs * $(n : name) ^ 1 ]>)
| <[ $lhs / $(n : name) ]> => parseAliasExpr(<[ $lhs / $(n : name) ^ 1 ]>)
| _ => Message.FatalError($"Invalid unittypes syntax: expected \"UnitName = alias\", got $decl")
};
CT.PowersMap[n.Id] = parseAliasExpr(aliasExpr);
(n.Id, 1.0)
| _ => Message.FatalError($"Invalid unittypes syntax: expected \"UnitName [: (coefficient expr)]\", got $decl")
};
units ::= unitCoef[0];
// Add new normalization coefficient into map
coefSubMap[unitCoef[0]] = unitCoef[1];
index++
};
units = units.Reverse();
when (initBaseUnits)
{
CT.AllUnits = units
}
// Generate corresponding concrete types
def units = if (units.Length >= CT.BaseUnits.Length) CT.AllUnits else units;
foreach (n in units)
{
def powers = CT.PowersMap[n];
mutable coef = 0.0;
when (!coefSubMap.TryGetValue(n, out coef))
{
coef = 1.0;
// Calculate coefficient from base unit coefficients on the fly
List.Iter2(
powers,
CT.BaseUnits,
fun (power : int, name)
{
when (power != 0)
{
def f = if (power > 0) _ * _ else _ / _;
coef = f(coef, Math.Pow(coefSubMap[name], Math.Abs(power)));
}
})
}
_ = CT.GetUnitType(
GlobalEnv.Core,
$"$(CT.UnitsNamespace).$namespaceName.$n",
powers,
coef)
}
| _ => Message.FatalError($"Invalid unittypes syntax: expected semicolon-separated type names, got $body")
};
<[ () ]>
}
}
OperatorMacros.n:
using Nemerle.Collections;
using Nemerle.Compiler;
using Nemerle.Compiler.Typedtree;
using Nemerle.Macros;
namespace Oyster.Units.Macros
{
using CT = CompileTimeUtil;
/// Multiplies two units. Inserts usual * if operands are of wrong type
macro @*(lexpr, rexpr)
{
CT.MulDivMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"*",
<[ $lexpr * $rexpr ]>,
_ + _,
_ * _,
<[ double.op_Multiply($lexpr.Value, $rexpr.Value) ]>)
}
/// Divides two units. Inserts usual / if operands are of wrong type
macro @/(lexpr, rexpr)
{
CT.MulDivMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"/",
<[ $lexpr / $rexpr ]>,
_ - _,
_ / _,
<[ double.op_Division($lexpr.Value, $rexpr.Value) ]>)
}
/// Sums two units. Inserts usual + if operands are of wrong type.
/// Throws compiler error if unit dimensions are not equal
macro @+(lexpr, rexpr)
{
CT.AddSubMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"+",
<[ $lexpr + $rexpr ]>,
fun(arg2Expr) { <[ double.op_Addition($lexpr.Value, $arg2Expr) ]> })
}
/// Subtracts two units. Inserts usual - if operands are of wrong type.
/// Throws compiler error if unit dimensions are not equal
macro @-(lexpr, rexpr)
{
CT.AddSubMacroBody(
ImplicitCTX(),
lexpr,
rexpr,
"-",
<[ $lexpr - $rexpr ]>,
fun(arg2Expr) { <[ double.op_Subtraction($lexpr.Value, $arg2Expr) ]> })
}
}
На всякий случай вот solution для VS 2005, которым пользовался я: Oyster.Units.zip. Напомню, что проекты были изготовлены с использованием шаблонов проектов, доступных тут: Re: Микро-аддин для VS 2005
Здравствуйте, Oyster, Вы писали:
O>Переписывание кода на генерацую value-types — это может ускорить код по понятным причинам. Для этого можно использовать интерфейсы и немного покрутить с генериками
Собсно, без value-типов временный незачет, ждем-с окончательной версии.
O>Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того)
Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, vdimas, Вы писали:
V>Собсно, без value-типов временный незачет, ждем-с окончательной версии.
Почему? Боишься за перфоманс? Советую для начала попробовать — много маленьких объектов должны выделяться в управляемой куче быстро.
Я сделаю это изменение, когда будет время. Как ты понимаешь, времени у нас у всех не так много по жизни... я и так слишком много времени потратил на этот код (в основном борясь с собственным незнанием языка). Да, и поверь мне на слово — эту задачу можно решить на value-types — просто это будет чуть сложнее, возможно.
И всё-таки мне бы хотелось услышать твоё общее мнение о коде. Как тебе описание самих величин, например? Имхо как минимум понятнее, чем на C++, и менее подвержено человеческим ошибкам при вводе.
O>>Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того) V>Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
Так прыгни и напиши
Это можно сделать — на Nemerle довольно серьёзные средства программирования, т.ч. для добавления своего синтаксиса. Единственное "но" — это выглядело бы как-то так (можно и с пробелами и без):
physops {
def res = 100 kg * 7 sec
}
Или так (имхо ненамного удобнее):
def res = kg 100 * sec 7
Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
def res = 100 kg * 7 sec
Re[3]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, Oyster, Вы писали:
O>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
O>И всё-таки мне бы хотелось услышать твоё общее мнение о коде. Как тебе описание самих величин, например? Имхо как минимум понятнее, чем на C++, и менее подвержено человеческим ошибкам при вводе.
Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.
O>>>Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того) V>>Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
[...]
O>
O>def res = kg 100 * sec 7
O>
O>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
O>
O>def res = 100 kg * 7 sec
O>
Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: [Nemerle] Семантический контроль над размерностями
Здравствуйте, vdimas, Вы писали:
V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.
Хмм... не сказал бы. Мне было тяжело понять код на C++, хотя опыт написания кода на C++ у меня есть.
O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
V>Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.
Здравствуйте, vdimas, Вы писали:
V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.
Здравствуйте, Oyster, Вы писали:
O>А ведь ты прав! Очень даже изящное решение получается, спасибо!
Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.
O>Интересненько? А точно получится использовать унарный постфиксный (не префиксный) оператор?
Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.
O>Кстати, в таком случае встаёт вопрос генерации макроса внутри макроса чтобы не хардкодить.
Боюсь, что генерация это слишком сложный путь. Одно дело сгенерировать код, а другое дело его скомпилировать. Можно конечно компилировать "настройки" в отдельную сборку и оттуда использовать.
Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).
Вот дамп кода(на псевдо-немерле , выглядит жутко конечно, но понять можно ) методов макро-оператора kg. Кстати стоит обратить внимание, что по сути это вполне обычный макрос, а оператором его делает атрибут сборки. В модуле LibrariesLoader есть функции которые сканируют сборку на предмет нахождения в ней макросов и таких атрибутов, и регистрируют их в MacroRegistry.
Здравствуйте, Vermicious Knid, Вы писали:
VK>Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.
+1. Я просто не сразу понял
VK>Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.
Да, я понял это, но не сразу. Спасибо
VK>Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).
+1. Интересная идея. Так действительно проще.
Re[6]: [Nemerle] Семантический контроль над размерностями
Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.
На то они и внесистемные...
Например, одних только давлений сколько...
— бар (100кПа = 1000гПа)
— ат (1кгс/см2 = 981гПа = 1000ммвс)
— атм (760ммрс = 1015гПа)
— просто мм ртутного и водяного столба, не говоря уж о psi и т.п.
Не заводить же под каждое из них свою систему?
Posted via RSDN NNTP Server 2.0
Перекуём баги на фичи!
Re[4]: [Nemerle] Семантический контроль над размерностями
O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):
VK>А по-моему выйдет. VK>
Здравствуйте, Кодт, Вы писали:
К>Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext. К>На то они и внесистемные...
К>Например, одних только давлений сколько... К>- бар (100кПа = 1000гПа) К>- ат (1кгс/см2 = 981гПа = 1000ммвс) К>- атм (760ммрс = 1015гПа) К>- просто мм ртутного и водяного столба, не говоря уж о psi и т.п. К>Не заводить же под каждое из них свою систему?
Да, ты прав. Я тупо скопировал постановку задачи с C++, поэтому вот так вот. На самом деле, можно немного доработать напильником и делать специальные алиасы в каком-то одном пространстве имён.
Хотя... с точки зрения CLR пространств имён вообще не существует — это просто часть имени класса. Так что не уверен, что это надо. Разве что немного поменять синтаксис самого макроса для удобства...
Re[5]: [Nemerle] Семантический контроль над размерностями
Здорово! Хоть у меня времени и нет практически, а все ж выделю — на выходных поизучаю код.
У меня уже почти готова следующая версия. Основные отличия от предыдущей:
1. Отсутствует ограничение на количество базовых типов (ортов). Более того, орты задаются теперь не номером в векторе, а неявно (с помощью препроцессора, увы).
2. Появились типизированные константы для перевода единиц в/из системы измерения с частично совпадающими ортами (отдельное спасибо Егору за высказанные замечания по переводу электромагнитных величин из СИ в СГС).
3. Наконец, библиотека просто стала на порядок удобнее в использовании.
Тем не менее, у нее есть некоторые недостатки. Фактически игра ведется на грани возможностей языка. Поэтому мне было очень интересно посмотреть на твой код.
Предлагаю следующий вариант развития событий. У меня есть определенные мысли по развитию подобного инструмента, я их частично воплотил в новой версии. Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса. Весьма возможно, что кое-что из того, что мне далось с таким трудом на C++, будет легко сделать на Nemerle.
В течении недели я надеюсь причесать новую версию и довести ее до состояния, достаточного для выкладывания в public domain.
... << RSDN@Home 1.1.4 stable rev. 510>>
Re: [Nemerle] Семантический контроль над размерностями