[Nemerle] Семантический контроль над размерностями
От: Oysterhttps://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 группы:

  • Исходники макробиблиотеки
  • Описание физических величин на простеньком DSL-е

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



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

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

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

  • Рефакторинг
  • Переписывание кода на генерацую value-types — это может ускорить код по понятным причинам. Для этого можно использовать интерфейсы и немного покрутить с генериками
  • Добавление большего количества метаинформации к генерируемым классам для формирования более информативных сообщений об ошибках
  • Переписывание макрооператоров. Сейчас, например, при использовании макрооператоров из пространства имён Oyster.Units.Macros невозможно пользоваться т.н. каррингом в стиле Nemerle
    Автор: Vermicious Knid
    Дата: 25.03.06
    , т.е. выражениями вида _ + _
  • Добавление синтаксического сахара вроде физических литералов (например 100kg * 15sec или что-то вроде того)

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

Надеюсь, сообщение оказалось полезным. И сорри всем, кто дочитал до этой строчки, за объём текста
Исходники макробиблиотеки
От: Oysterhttps://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) ]> })
    }
}
Описание физических величин
От: Oysterhttps://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
От: Oysterhttps://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] Семантический контроль над размерностями
    От: Oysterhttps://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] Семантический контроль над размерностями
    От: Oysterhttps://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] Семантический контроль над размерностями
    От: Oysterhttps://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] Семантический контроль над размерностями
    От: Oysterhttps://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] Семантический контроль над размерностями
    От: Oysterhttps://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] Семантический контроль над размерностями
    От: Oysterhttps://github.com/devoyster
    Дата: 05.04.06 12:42
    Оценка:
    Здравствуйте, Кодт, Вы писали:

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

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

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

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

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

    Хотя... с точки зрения CLR пространств имён вообще не существует — это просто часть имени класса. Так что не уверен, что это надо. Разве что немного поменять синтаксис самого макроса для удобства...
    Re[5]: [Nemerle] Семантический контроль над размерностями
    От: Oysterhttps://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 rsdnwww.nemerle.org
    Дата: 06.04.06 00:46
    Оценка: :)
    Здравствуйте, Oyster, Вы писали:

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

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

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