[Nemerle] Еще один пример - еще одни вопросы
От: Mckey Россия  
Дата: 28.07.06 06:21
Оценка:
Была задача описать источники данных и категории данных в этих источниках
и связать все это с метками описывающие все это в каком-либо внешнем файле...
Плюс тут в некоторой степени решена проблема (как мне кажется)
уже звучавшая на этом форуме о перекодировке значений одного enum-а
в значения другого enum-а. Более удобная чем то что там предлагали.
Необходимо всего лишь добавить Extension метод с нужной функциональностью.


using System;
using System.Console;
using Nemerle.Collections;
using Nemerle.DesignPatterns;

// Описываем два enum с атрибутами источника и сатегорий

    [SourceLabel("CK")]
    public enum CK
    {
        | Unknown
        [CategoryLabel("TI")]
        | TI
        [CategoryLabel("TS")]
        | TS
        [CategoryLabel("AVG")]
        | Average
        [CategoryLabel("INST")]
        | Instant
        [CategoryLabel("INTG")]
        | Integral
        [CategoryLabel("DAY")]
        | Days
        [CategoryLabel("PLAN")]
        | Plan
        [CategoryLabel("TIF")]
        | TIF
        [CategoryLabel("UHI30")]
        | UHI30
        [CategoryLabel("UHIDAY")]
        | UHIDay
    }
    
    [SourceLabel("INGIS")]
    public enum INGIS
    {
        | Unknown
        [CategoryLabel("I")]
        | Izm
        [CategoryLabel("S")]
        | Sum
        [CategoryLabel("DI")]
        | DayIzm
        [CategoryLabel("DS")]
        | DaySum
        [CategoryLabel("MI")]
        | MonthIzm
        [CategoryLabel("MS")]
        | MonthSum
    }
    
// и третий для примера и сравнения что все у нас работает правильно
    
    public enum Check
    {
        | Checked
        | Unchecked
    }

// Класс Аттрибут для описания источника данных
// + процедура для извлечения этого аттрибута из всех enum-ов
    public class SourceLabelAttribute : Attribute
    {
    private _label: string;
    
        public this(label: string) {_label = label;}

    public Label: string {get{_label;}}

    // Сама процедура сделана как Экстеншион метод, т.е. добавляет функциональность ко всем Enum-ам
        // самый обыкновенный рефлекшн ничего особенного, единственно что интересно - передача функции в Array.Find
        public static GetSourceLabel(this enumObj: Enum): string
    {
      def attribList = enumObj.GetType().GetCustomAttributes(false);
            def attrib = Array.Find(attribList,_ is SourceLabelAttribute);
      if (attrib != null)
                (attrib:>SourceLabelAttribute).Label
            else
                String.Empty
    }
    }

// Класс Аттрибут для описания категорий данных
// + процедура для извлечения этого аттрибута из всех enum-ов
// Отличаеться от предыдушего тем что кастомный атрибут береться не от типа, а от поля этого типа
// и немного другая процедура возврашения категории данных
    public class CategoryLabelAttribute : Attribute
    {
    private _label: string;
    
        public this(label: string) {_label = label;}

    public Label: string {get {_label;}}

    public static GetCategoryLabel(this enumObj: Enum): string
    {
      def attribList = enumObj.GetType().GetField(enumObj.ToString()).GetCustomAttributes(false);
      def attrib = Array.Find(attribList,_ is CategoryLabelAttribute);
            if (attrib != null)
                String.Join("_",array[enumObj.GetSourceLabel(),(attrib:>CategoryLabelAttribute).Label])
            else
                String.Join("_",array[enumObj.GetSourceLabel(),"Unknown"])
    }
    }
    
// Нечтно вроде перекодировочной таблицы между метками источников и категории и самим типом enum-ов
// т.е. была нужна следующая функциональность - парсится какая-то таблица с метками источников и категорий данных
// На основе меток создаються Enum-ы, которые затем передаються в виде параметров в классы непосредственно хранящие
// извлеченные данные и работающие с этими данными
    public module Category_Label
    {
        // для перекодировки храним связку трех значений
        // имя типа Enum-а - чтобы не добавлять повторных типов
       // непосредственно сам Enum
        // и соотвествующую ему метку с источником и категорией данных
       // можно было бы сделать и через Hashtable, 
       // но там был сложнее поиск существования источника данных (хотя надо будет еще раз попробовать)
        private mutable l_LSC: list[string*Enum*string] = [];
        
        // процедура регистрации типов с источниками и категориями данных
        public RegSourceEnum(ti: Type): void
        {
            def ContainSource(sourcename)
            {
                l_LSC.Exists(fun(x,_,_){x == sourcename})
            }
            def GetLSC(en)
            {
                (en.GetSourceLabel(),en,en.GetCategoryLabel())
            }
            
            // необходимые проверки на правильность переданного типа
            when (ti == null) throw ArgumentNullException("Должен быть передан тип");
                  when (!ti.IsEnum) throw ArgumentException("Должен быть указан Enum с категориями данных");
            // здесь, если тип не помечен аттрибутов истоника, можно кинуть исключение, а можно просто ничего не делать
            when (Array.Find(ti.GetCustomAttributes(false),_ is SourceLabelAttribute) == null) {}
            //    throw ArgumentException("Enum должен быть помечен аттрибутом [SourceLabel(""___"")]");
            // Если типа еще нет в перекодировочной таблице - то добавляем все его категории
            when (!ContainSource(ti.GetType().Name))
            // пришлось делать через foreach
                // потому что не через Array.ForEach ни через какие либо другие функции работы с групповыми операциями
                // сделать подобное не удалось, либо выглядело это намного длиннее и менее понятно
                // может кто-то предложет способ сделать это получше - хотя мне кажеться и так замечательно
                foreach(val:>Enum in Enum.GetValues(ti))
                    l_LSC = GetLSC(val)::l_LSC
        }
        
        // Просто для отладки или еще для чего может пригодиться
        // например чтобы где-нить вывести все возможные категории и источники данных и соотвествующие им метки
        public CategoryLabelList: list[Enum*string]
        {
            get
            {
                l_LSC.FoldLeft([],fun((_,b,c),y){(b,c)::y})
            }
        }
        
        // Получаем Enum с категорией по метке - основная задача перекодировочной таблицы
        public CategoryByLabel[T](label: string): T where T: enum
        {
                // ищем метку и возвращаем что надо
            match (l_LSC.Find(fun(a,_,b){b == label && a == typeof(T).Name}))
            {
                | Some((_,x,_)) => x:>T
                | _ => 0:>T
            }
        }
        
        // Обратная задача - по Enum-у получаем метку
       // почему нельзя сделать сразу cat.GetCategoryLabel()
        // потому что желательно получать метку только от зарегестрированных типов
        public LabelByCategory[T](cat: T): string where T: enum
        {
                 // ищем Enum и возврщаем метку
            match(l_LSC.Find(fun(_,y,_){y.Equals(cat)}))
            {
                | Some((_,_,x)) => x
                | _ => String.Empty
            }
        }
    }
    
    public module MainApp
    {
        Main(): void
        {
            // По моему второй вариант проще и в понимании и в написании
            WriteLine(SourceLabelAttribute.GetSourceLabel(CK.TI));
            WriteLine(CK.Unknown.GetSourceLabel());
            WriteLine(INGIS.Izm.GetSourceLabel());
            // Проверка enum-а с отсутствующим аттрибутом
            WriteLine(Check.Checked.GetSourceLabel());
            WriteLine(CategoryLabelAttribute.GetCategoryLabel(CK.Average));
            WriteLine(CategoryLabelAttribute.GetCategoryLabel(INGIS.DaySum));
            WriteLine(CK.TI.GetCategoryLabel());
            WriteLine(CK.Unknown.GetCategoryLabel());
            // Проверка enum-а с отсутствующим аттрибутом
            WriteLine(Check.Unchecked.GetCategoryLabel());
            // Регистрируем типы
            Category_Label.RegSourceEnum(typeof(CK));
            Category_Label.RegSourceEnum(typeof(INGIS));
            // Две следующих регистрации будут проигнорированы
            Category_Label.RegSourceEnum(typeof(INGIS));
            Category_Label.RegSourceEnum(typeof(Check));
            mutable cat1: CK;
            mutable cat2: INGIS;
            mutable str: string;
            // Различные виды перекодировки
            cat1 = Category_Label.CategoryByLabel("CK_TI");
            str = Category_Label.LabelByCategory(cat1);
            WriteLine($"$cat1 - $str");
            cat1 = Category_Label.CategoryByLabel("CK_AVG");
            str = Category_Label.LabelByCategory(cat1);
            WriteLine($"$cat1 - $str");
            // Пытаемся вернуть тип СК по метке от типа ИНГИС
            // Осуществляем какой-никакой контроль типов
         // т.е. возвращаем CK.Unknown
            cat1 = Category_Label.CategoryByLabel("INGIS_I");
            str = Category_Label.LabelByCategory(cat1);
            WriteLine($"$cat1 - $str");
            cat2 = Category_Label.CategoryByLabel("INGIS_I");
            str = Category_Label.LabelByCategory(cat2);
            WriteLine($"$cat2 - $str");
            cat2 = Category_Label.CategoryByLabel("INGIS_MS");
            str = Category_Label.LabelByCategory(cat2);
            WriteLine($"$cat2 - $str");
            cat2 = Category_Label.CategoryByLabel("INGIS_Unknown");
            str = Category_Label.LabelByCategory(cat2);
            WriteLine($"$cat2 - $str");
            // Аналогично вышесказанному для несуществующей метки
            cat2 = Category_Label.CategoryByLabel("Sasha");
            str = Category_Label.LabelByCategory(cat2);
            WriteLine($"$cat2 - $str");
            // Для справки выводим все зарегистрированные метки и категории
            WriteLine(Category_Label.CategoryLabelList)
        }
    }


Какие задачи не удалось решить и почему:
1. Хотелось бы иметь (не мне ) перекодировки в виде проперти...
Проблема: У статических классов не может быть индексированных проперти..
Попытался сделать Singleton — получилось, но не совсем...
ибо нельзя сделать типизированный проперти.. (или это просто я не знаю как это делаеться)
т.е. такое невозможно

public LabelByCategory[T][cat: T]: string where T: enum


Хотя надо еще попробовать такое:

public LabelByCategory.[T][cat: T]: string where T: enum


Но что-то мне подсказывает что и такое не прокатит...

30.01.07 18:28: Перенесено модератором из 'Декларативное программирование' — IT
Делай добро и бросай его в воду...
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.