[Nemerle] Семантический контроль над размерностями
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 07: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++. Да и описание физических величин имхо выглядит удобнее (см. исходники).

Надеюсь, сообщение оказалось полезным. И сорри всем, кто дочитал до этой строчки, за объём текста
Исходники макробиблиотеки
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 07:06
Оценка:
ConcreteUnitTypeAttribute.n:

using System; using Nemerle.Utility; namespace Oyster.Units { /// Automatically attached to all concrete unit type implemetations public 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 types abstract public class UnitTypeBase { /// Unit value as double [Accessor] _value : double; /// Constructs given unit type instance from any other unit type instance protected this(value : UnitTypeBase) { // Perform normalization-denormalization here this(value._value * value.NormalizationCoef / NormalizationCoef) } /// Constructs given unit type instance from double protected this(value : double) { _value = value } /// Unit type normalization coefficient abstract public NormalizationCoef : double { get; } /// ToString() override override 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 information module CompileTimeUtil { /// Units NS name public UnitsNamespace = "Oyster.Units"; /// Stores list of base units (such as mass or length) registered in the system public mutable BaseUnits : list[string]; /// Stores list of all registered units including aliases public mutable AllUnits : list[string]; /// Stores powers list for given base unit or alias public 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 coefficient public 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 namespace public 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 list public 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 powers public GenerateUnitTypeSuffix(baseUnitPowers : list[int]) : string { | [] => string.Empty | _ => "_" + baseUnitPowers.Head.ToString() + GenerateUnitTypeSuffix(baseUnitPowers.Tail) } /// Parses abstract unit type suffix public ParseUnitTypeSuffix(typeName : string) : list[int] { List.Map(NString.Split(typeName.Substring(typeName.IndexOf('_') + 1), '_'), int.Parse) } /// Returns PExpr type public GetExprType(ctx : Typer, expr : PExpr) : TypeInfo { ctx.TypeExpr(expr).ty.FixedValue.TypeInfo } /// Returns normalization coef if type is concrete unit type public 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 macros public 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 / macros public 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 - macros public 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) ]> }) } }

Описание физических величин
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 07:08
Оценка:
UnitDef.n:

using Oyster.Units.Macros; public module UnitDef { public Def() : void { // SI unittypes Si { // Base units Mass; Length; Time; Temperature; CurrentStrength; LightIntensity; QuantityOfSubstance; // Aliases Area = Length ^ 2; Volume = Area * Length; Velocity = Length / Time; Acceleration = Velocity / Time; AngularVelocity = Velocity; AngularAcceleration = Acceleration; Density = Mass / Volume; Force = Mass * Acceleration; Pressure = Mass / Length / Time ^ 2; SpecificGravity = Pressure / Length; LinearMomentum = Mass * Velocity; MomentOfInertia = Mass * Area; Energy = Force * Length; Power = Energy / Time; DynamicViscosity = Mass / Length / Time; KinematicViscosity = DynamicViscosity; HeatCapacity = Energy / Temperature; Entropy = HeatCapacity; SpecificHeat = HeatCapacity / Mass; SpecificEntropy = SpecificHeat; Charge = Time * CurrentStrength; LinearDensityOfCharge = Charge / Length; SurfaceDensityOfCharge = Charge / Area; ElectricFluxDensity = SurfaceDensityOfCharge; PackedDensityOfCharge = Charge / Volume; Voltage = Power / CurrentStrength; ElectricForce = Force / CurrentStrength; Resistance = Voltage / CurrentStrength; SpecificResistance = Resistance * Length; Permittance = Time / Resistance; CurrentDensity = CurrentStrength / Length ^ 2 }; // CGS unittypes Cgs { Mass : (1.0 / 1000.0); Length : (1.0 / 100.0); Time; Temperature; CurrentStrength : (1.0 / 3000000000.0); LightIntensity; QuantityOfSubstance }; // EXT unittypes Ext { Power : (1471.0 / 2.0) } } }

Все исходники в solution для VS 2005
От: Oyster Украина https://github.com/devoyster
Дата: 05.04.06 07:47
Оценка:
На всякий случай вот solution для VS 2005, которым пользовался я: Oyster.Units.zip. Напомню, что проекты были изготовлены с использованием шаблонов проектов, доступных тут: Re: Микро-аддин для VS 2005
Автор: Oyster
Дата: 06.03.06
.
Re: [Nemerle] Семантический контроль над размерностями
От: vdimas Россия  
Дата: 05.04.06 08:32
Оценка:
Здравствуйте, Oyster, Вы писали:

O>
  • Переписывание кода на генерацую value-types — это может ускорить код по понятным причинам. Для этого можно использовать интерфейсы и немного покрутить с генериками

    Собсно, без value-типов временный незачет, ждем-с окончательной версии.

    O>
  • Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того)
    Это было бы такое WOW , что вполне возможно, я бы прыгнул на Немерле.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
  • Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 08:43
    Оценка:
    Здравствуйте, 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] Семантический контроль над размерностями
    От: Vermicious Knid  
    Дата: 05.04.06 09:12
    Оценка: 54 (3)
    Здравствуйте, Oyster, Вы писали:

    O>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):


    А по-моему выйдет.

    namespace My.Phys { [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "kg", true, 283, 284)] [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "sec", true, 283, 284)] macro @kg(val) { <[ do_something_crazy_with_kg($val) ]> } macro @sec(val) { <[ do_something_crazy_with_sec($val) ]> } }



    using System; using My.Phys; module Main { do_something_crazy_with_kg(val : double) : double { val } do_something_crazy_with_sec(val : double) : double { val } Main() : void { Console.WriteLine(100.0 kg); Console.WriteLine(100.0 kg); Console.WriteLine(20.0 sec); Console.WriteLine(100.0 kg * 20.0 sec); } }

    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 10:04
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>А по-моему выйдет.


    А ведь ты прав! Очень даже изящное решение получается, спасибо!

    Кстати, в таком случае встаёт вопрос генерации макроса внутри макроса чтобы не хардкодить.
    Re[3]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 05.04.06 10:10
    Оценка:
    Здравствуйте, Oyster, Вы писали:


    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] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 10:27
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.


    Хмм... не сказал бы. Мне было тяжело понять код на C++, хотя опыт написания кода на C++ у меня есть.

    O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):


    V>Жаль... я именно это и имел ввиду. Спасибо за своевременный ответ.


    Я ошибся : Re[3]: [Nemerle] Семантический контроль над размерностями
    Автор: Vermicious Knid
    Дата: 05.04.06
    .
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 10:50
    Оценка:
    Здравствуйте, vdimas, Вы писали:

    V>Описание самих величин — понятнее. А вот код, где вся механика происходит — не в пример запутаннее. В то время как в варианте на С++ все понятно.


    Кстати, должен заметить, что мой код избавлен от двух первых проблем, описанных тут: Направления дальнейшего развития
    Автор: CrystaX
    Дата: 21.11.05
    . Так что это уже не точно тот же код.
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: Vermicious Knid  
    Дата: 05.04.06 11:04
    Оценка: 7 (1)
    Здравствуйте, Oyster, Вы писали:

    O>А ведь ты прав! Очень даже изящное решение получается, спасибо!


    Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.

    O>Интересненько? А точно получится использовать унарный постфиксный (не префиксный) оператор?


    Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.

    using Nemerle.English; def yoda = true not; def reallyObfuscated = yoda !; def ohmy = 23 ~;



    O>Кстати, в таком случае встаёт вопрос генерации макроса внутри макроса чтобы не хардкодить.


    Боюсь, что генерация это слишком сложный путь. Одно дело сгенерировать код, а другое дело его скомпилировать. Можно конечно компилировать "настройки" в отдельную сборку и оттуда использовать.

    Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).

    Вот дамп кода(на псевдо-немерле , выглядит жутко конечно, но понять можно ) методов макро-оператора kg. Кстати стоит обратить внимание, что по сути это вполне обычный макрос, а оператором его делает атрибут сборки. В модуле LibrariesLoader есть функции которые сканируют сборку на предмет нахождения в ней макросов и таких атрибутов, и регистрируют их в MacroRegistry.

    Public, Sealed, Macro, SpecialName My.Phys.kgMacro : System.Object, Nemerle.Compiler.IMacro { Private, Static method My.Phys.kgMacro..cctor() : void { { def customs = typeof (kgMacro).GetCustomAttributes (false); foreach (in ((x is MacroUsageAttribute), customs)) my_usage = x; keywords = [] } Public, SpecialName method My.Phys.kgMacro.get_IsInherited() : bool { my_usage != null && my_usage.Inherited } Public, SpecialName method My.Phys.kgMacro.get_Keywords() : list[string] { keywords } Public, SpecialName method My.Phys.kgMacro.get_Usage() : Nemerle.MacroUsageAttribute { my_usage } Public, SpecialName method My.Phys.kgMacro.get_IsTailRecursionTransparent() : bool { false } Public method My.Phys.kgMacro.GetName() : string { "kg" } Public method My.Phys.kgMacro.GetNamespace() : string { "My.Phys" } Public method My.Phys.kgMacro.SyntaxExtension() : (Nemerle.Compiler.GrammarElement * list[Nemerle.Compiler.Parsetree.SyntaxElement] -> list[Nemerle.Compiler.Parsetree.SyntaxElement]) { (null, ((Nemerle.Utility.Identity.[list[SyntaxElement], list[SyntaxElement]] () : object) :> list[SyntaxElement] -> list[SyntaxElement])) } Public method My.Phys.kgMacro.Run(_N_944 : Nemerle.Compiler.Typer, parms : list[Nemerle.Compiler.Parsetree.SyntaxElement]) : Nemerle.Compiler.Parsetree.PExpr { match (parms) { | [SyntaxElement.Expression (val)] => <[ do_something_crazy_with_kg ($(val)) ]> | _ => def len = List.Length (parms); def types = parms.ToString (", "); Message.FatalError ("macro `" + "kg" + "' expects following list of arguments: (" + "PExpr" + ") got some " + len.ToString () + " parameters [" + types + "]") } } Public method My.Phys.kgMacro.CallTransform(trans_p : list[Nemerle.Compiler.Parsetree.PExpr]) : list[Nemerle.Compiler.Parsetree.SyntaxElement] { { mutable trans_res = []; match (trans_p) { | x :: trans_p => { trans_res = SyntaxElement.Expression (x) :: trans_res; match (trans_p) { | x :: _ => trans_res = SyntaxElement.Expression (x) :: trans_res | [] => () } } | [] => () }; List.Rev (trans_res) } }

    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 11:13
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>Удалил старое сообщение? Видимо заметил пример в конце. Ну да ладно.


    +1. Я просто не сразу понял

    VK>Точно. Это в общем-то фича языка — нет разницы между постфиксными и префиксными операторами.


    Да, я понял это, но не сразу. Спасибо

    VK>Но есть идея и более простая. И макрос, и оператор можно зарегистрировать вручную через API компилятора(см. модуль MacroRegistry). Макрос это по сути экземпляр некого класса, реализовывающего интерфейс IMacro. Нужно просто написать собственную реализацию IMacro вручную(т.е. не использовать встроенную в компилятор генерацию этого класса), которая в зависимости от того как ее инициализируют будет вести себя как разные макросы(т.е. с разными именами и генерировать различный код).


    +1. Интересная идея. Так действительно проще.
    Re[6]: [Nemerle] Семантический контроль над размерностями
    От: Vermicious Knid  
    Дата: 05.04.06 11:14
    Оценка: 1 (1)
    Здравствуйте, Vermicious Knid, Вы писали:

    VK>Вот дамп кода(на псевдо-немерле , выглядит жутко конечно, но понять можно ) методов макро-оператора kg.


    Вот еще дамп из рефлектора, чтобы было понятнее:

    public sealed class kgMacro : IMacro { // Methods static kgMacro(); public kgMacro(); public list<SyntaxElement> CallTransform(list<PExpr> trans_p); public string GetName(); public string GetNamespace(); public PExpr Run(Typer _N_944, list<SyntaxElement> parms); public Tuple<GrammarElement, Function<list<SyntaxElement>, list<SyntaxElement>>> SyntaxExtension(); // Properties public bool IsInherited { get; } public bool IsTailRecursionTransparent { get; } public list<string> Keywords { get; } public MacroUsageAttribute Usage { get; } // Fields [Immutable] private static list<string> keywords; private static MacroUsageAttribute my_usage; }

    Re: [Nemerle] Семантический контроль над размерностями
    От: Кодт Россия  
    Дата: 05.04.06 12:38
    Оценка:
    Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.
    На то они и внесистемные...

    Например, одних только давлений сколько...
    — бар (100кПа = 1000гПа)
    — ат (1кгс/см2 = 981гПа = 1000ммвс)
    — атм (760ммрс = 1015гПа)
    — просто мм ртутного и водяного столба, не говоря уж о psi и т.п.
    Не заводить же под каждое из них свою систему?
    Posted via RSDN NNTP Server 2.0
    Перекуём баги на фичи!
    Re[4]: [Nemerle] Семантический контроль над размерностями
    От: vdimas Россия  
    Дата: 05.04.06 12:39
    Оценка:
    Здравствуйте, Vermicious Knid, Вы писали:


    O>>Но так — без обрамляющего макроса и с величинами, заданными постфиксом, — по-моему точно не выйдет (у всего есть предел ):


    VK>А по-моему выйдет.

    VK>

    VK>namespace My.Phys VK>{ VK> [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "kg", true, 283, 284)] VK> [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "sec", true, 283, 284)] VK>



    Что это за шаманство?
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re[2]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 12:42
    Оценка:
    Здравствуйте, Кодт, Вы писали:

    К>Немного отдаёт неряшливостью, что внесистемные единицы помещены в систему Ext.

    К>На то они и внесистемные...

    К>Например, одних только давлений сколько...

    К>- бар (100кПа = 1000гПа)
    К>- ат (1кгс/см2 = 981гПа = 1000ммвс)
    К>- атм (760ммрс = 1015гПа)
    К>- просто мм ртутного и водяного столба, не говоря уж о psi и т.п.
    К>Не заводить же под каждое из них свою систему?

    Да, ты прав. Я тупо скопировал постановку задачи с C++, поэтому вот так вот. На самом деле, можно немного доработать напильником и делать специальные алиасы в каком-то одном пространстве имён.

    Хотя... с точки зрения CLR пространств имён вообще не существует — это просто часть имени класса. Так что не уверен, что это надо. Разве что немного поменять синтаксис самого макроса для удобства...
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: Oyster Украина https://github.com/devoyster
    Дата: 05.04.06 12:43
    Оценка: 8 (1)
    Здравствуйте, vdimas, Вы писали:

    VK>>А по-моему выйдет.

    VK>>namespace My.Phys VK>>{ VK>> [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "kg", true, 283, 284)] VK>> [assembly: Nemerle.Internal.OperatorAttribute ("My.Phys", "sec", true, 283, 284)]


    V>Что это за шаманство?

    Задание приоритетов для своих операторов — надо же их как-то задавать
    Re: [Nemerle] Семантический контроль над размерностями
    От: CrystaX Россия  
    Дата: 05.04.06 20:02
    Оценка:
    Здравствуйте, Oyster, Вы писали:

    Здорово! Хоть у меня времени и нет практически, а все ж выделю — на выходных поизучаю код.
    У меня уже почти готова следующая версия. Основные отличия от предыдущей:
    1. Отсутствует ограничение на количество базовых типов (ортов). Более того, орты задаются теперь не номером в векторе, а неявно (с помощью препроцессора, увы).
    2. Появились типизированные константы для перевода единиц в/из системы измерения с частично совпадающими ортами (отдельное спасибо Егору за высказанные замечания по переводу электромагнитных величин из СИ в СГС).
    3. Наконец, библиотека просто стала на порядок удобнее в использовании.

    Тем не менее, у нее есть некоторые недостатки. Фактически игра ведется на грани возможностей языка. Поэтому мне было очень интересно посмотреть на твой код.

    Предлагаю следующий вариант развития событий. У меня есть определенные мысли по развитию подобного инструмента, я их частично воплотил в новой версии. Было бы очень интересно и познавательно вести разработку параллельно — на C++ и на Nemerle. Исключительно из спортивного интереса. Весьма возможно, что кое-что из того, что мне далось с таким трудом на C++, будет легко сделать на Nemerle.

    В течении недели я надеюсь причесать новую версию и довести ее до состояния, достаточного для выкладывания в public domain.
    ... << RSDN@Home 1.1.4 stable rev. 510>>
    Re: [Nemerle] Семантический контроль над размерностями
    От: VladD2 Российская Империя www.nemerle.org
    Дата: 06.04.06 00:46
    Оценка: :)
    Здравствуйте, Oyster, Вы писали:

    Ну, ты герой! Такой объем работы в научных целях.

    Я бы ни в жизнь на такое не решился.
    ... << RSDN@Home 1.2.0 alpha rev. 637>>

    Все что нас не убивает, потом сильно об этом жалеет :).
    Подождите ...
    Wait...
    Пока на собственное сообщение не было ответов, его можно удалить.