Анонимные классы
От: hardcase Пират http://nemerle.org
Дата: 01.02.10 12:39
Оценка: 20 (6)
Допилил макрос, генерирующий анонимные классы в духе C#:
def obj = new (f1 = "asdasdf", f2 = 10, f3 = array[1.0, 2.0]);

Внутренне они выглядят аналогично C#-овым, с отличиями:
1) реализуют специальный интерфейс IAnonymous, позволяющий обращаться к полям по именам через индексатор;
2) можно сравнивать на эквивалентность экземпляры анонимных классов находящихся в разных сборках;
3) если полей больше одного, то генерируется метод ToTuple(), возвращающий кортеж;
4) есть синтаксис, позволяющий собрать анонимный класс из кортежа:
def t = (a, b);
def obj = new [x, y](t);
//или
def obj = new [x, y](a, b);

Пример потолще есть тут, исходники обитают в каталоге snippets.
/* иЗвиНите зА неРовнЫй поЧерК */
Re: Анонимные классы
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.02.10 14:42
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Допилил макрос, генерирующий анонимные классы в духе C#:

H>
H>def obj = new (f1 = "asdasdf", f2 = 10, f3 = array[1.0, 2.0]);
H>


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

Может быть имеет смысл сделать реализацию максимально близкой к C#-ной?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Анонимные классы
От: hardcase Пират http://nemerle.org
Дата: 01.02.10 14:56
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Анонимные классы в шарпе раскрываются в дженерик лкассы, что сделано (видимо) в целях экономии памяти и повторного реиспользования мета-информации.

Именно так и происходит.

VD>Плюс для анонимных типов производится специализированная обработка при поддержке Linq-а.


С этого места по подробнее. Что хочется видеть?
/* иЗвиНите зА неРовнЫй поЧерК */
Re[3]: Анонимные классы
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.02.10 15:34
Оценка:
Здравствуйте, hardcase, Вы писали:

VD>>Плюс для анонимных типов производится специализированная обработка при поддержке Linq-а.


H>С этого места по подробнее. Что хочется видеть?


Создай на C# сборку внутри которой используются анонимные типы. Причем, так чтобы были два анонимных типа у которых совпадают имена полей, но при этом типы полей все же отличаются. Затем декомпилируй полученную сборку с помощью рефлектора и погляди какие типы были сформированы для анонимных типов. Думаю, сразу все станет понятно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Анонимные классы
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.02.10 15:58
Оценка:
Здравствуйте, hardcase, Вы писали:

VD>>Анонимные классы в шарпе раскрываются в дженерик лкассы, что сделано (видимо) в целях экономии памяти и повторного реиспользования мета-информации.

H>Именно так и происходит.

Разве? Видимо я плохо изучил код.

VD>>Плюс для анонимных типов производится специализированная обработка при поддержке Linq-а.


H>С этого места по подробнее. Что хочется видеть?


Дело в том, что для анонимных типов компилятор C# делает допущение о равенстве параметров конструкторов и свойств. Скажем для такого кода:
using System;
using System.Linq;

class Program
{
  static void Main()
  {
    var ary = new[] { 1, 2, 3, 4, 5 };

    var res1 = ary.Select(x => new { x, dbl = x * x });

    foreach (var item in res1)
      Console.WriteLine(item);

    Console.WriteLine("-------------------------");

    var res2 = ary.Select(x => new { x = x.ToString(), dbl = x * x });

    foreach (var item in res2)
      Console.WriteLine(item);
  }
}

Компилятором C# будет сгенерирован вот такой тип:
[DebuggerDisplay(@"\{ x = {x}, dbl = {dbl} }", Type="<Anonymous Type>"), CompilerGenerated]
internal sealed class <>f__AnonymousType0<<x>j__TPar, <dbl>j__TPar>
{
    // Fields
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly <dbl>j__TPar <dbl>i__Field;
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly <x>j__TPar <x>i__Field;

    // Methods
    [DebuggerHidden]
    public <>f__AnonymousType0(<x>j__TPar x, <dbl>j__TPar dbl)
    {
        this.<x>i__Field = x;
        this.<dbl>i__Field = dbl;
    }

    [DebuggerHidden]
    public override bool Equals(object value)
    {
        var type = value as <>f__AnonymousType0<<x>j__TPar, <dbl>j__TPar>;
        return (((type != null) && EqualityComparer<<x>j__TPar>.Default.Equals(this.<x>i__Field, type.<x>i__Field)) && EqualityComparer<<dbl>j__TPar>.Default.Equals(this.<dbl>i__Field, type.<dbl>i__Field));
    }

    [DebuggerHidden]
    public override int GetHashCode()
    {
        int num = -1802186317;
        num = (-1521134295 * num) + EqualityComparer<<x>j__TPar>.Default.GetHashCode(this.<x>i__Field);
        return ((-1521134295 * num) + EqualityComparer<<dbl>j__TPar>.Default.GetHashCode(this.<dbl>i__Field));
    }

    [DebuggerHidden]
    public override string ToString()
    {
        StringBuilder builder = new StringBuilder();
        builder.Append("{ x = ");
        builder.Append(this.<x>i__Field);
        builder.Append(", dbl = ");
        builder.Append(this.<dbl>i__Field);
        builder.Append(" }");
        return builder.ToString();
    }

    // Properties
    public <dbl>j__TPar dbl
    {
        get
        {
            return this.<dbl>i__Field;
        }
    }

    public <x>j__TPar x
    {
        get
        {
            return this.<x>i__Field;
        }
    }
}


Я не знаю как происходит сопоставление, но компилятор знает, что для данного типа свойству "dbl" соответствует параметр "dbl" единственного конструктора.

Никакой разметки я не нашел, так что склонен полагать, что шарп просто специальным образом рассматривает анонимные типы. Это позволяет писать код типа следующего:
var res1 = ary.Select(x => new { x, dbl = x * x }).Select(y => y.dbl);

Если в таком коде вместо анонимных типов использовать нормальные и попытаться скомпилировать этот код с использованием, скажем, Linq 2 SQL, то будет выдана ошибка, так как провайдер не будет знать как сопоставить параметры конструктора и свойства объекта. Для анонимных типов же компилятор C# вызывает специальный метод:
static System.Linq.Expressions.Expression. NewExpression New(
  ConstructorInfo constructor, 
  IEnumerable<Expression> arguments, 
  params MemberInfo[] members)

где последним параметром передает список свойств соответствующих параметрам конструктора (описание которого передается первым параметром).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Анонимные классы
От: hardcase Пират http://nemerle.org
Дата: 01.02.10 16:03
Оценка: 8 (1)
Здравствуйте, VladD2, Вы писали:

VD>Здравствуйте, hardcase, Вы писали:


VD>>>Плюс для анонимных типов производится специализированная обработка при поддержке Linq-а.


H>>С этого места по подробнее. Что хочется видеть?


VD>Создай на C# сборку внутри которой используются анонимные типы. Причем, так чтобы были два анонимных типа у которых совпадают имена полей, но при этом типы полей все же отличаются. Затем декомпилируй полученную сборку с помощью рефлектора и погляди какие типы были сформированы для анонимных типов. Думаю, сразу все станет понятно.



Для кода на C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1 {
    class Program {
        static void Main(string[] args) {
            var obj1 = new { a = 10, b = "10" };
            Console.WriteLine(obj1.GetType().FullName);
            var obj2 = new { a = 10.0, b = "10" };
            Console.WriteLine(obj2.GetType().FullName);
        }
    }
}



Строится анонимный тип
[CompilerGenerated, DebuggerDisplay(@"\{ a = {a}, b = {b} }", Type="<Anonymous Type>")]
internal sealed class <>f__AnonymousType0<<a>j__TPar, <b>j__TPar>
{
    // Fields
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly <a>j__TPar <a>i__Field;
    [DebuggerBrowsable(DebuggerBrowsableState.Never)]
    private readonly <b>j__TPar <b>i__Field;

    // Methods
    [DebuggerHidden]
    public <>f__AnonymousType0(<a>j__TPar a, <b>j__TPar b);
    [DebuggerHidden]
    public override bool Equals(object value);
    [DebuggerHidden]
    public override int GetHashCode();
    [DebuggerHidden]
    public override string ToString();

    // Properties
    public <a>j__TPar a { get; }
    public <b>j__TPar b { get; }
}


В моей реализации на Nemerle:

using System;
using System.Console;
using Nemerle.Utility;

using Nemerle.Extensions;

module Program
{
  Main() : void
  {
    def obj1 = new ( a = 10, b = "10");
    WriteLine(obj1.GetType().FullName);
    def obj2 = new ( a = 10.0, b = "10");
    WriteLine(obj2.GetType().FullName);
  }
}


Создается также единственный класс вида (кое что можно убрать, интерфейс IAnonymous реализован эксплицитно)
[DebuggerDisplay(@"\{ a = {a}, b = {b} \}")]
internal sealed class _N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> : IEquatable<_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824>>, IAnonymous
{
    // Fields
    private readonly _N_a_2825 _a;
    private readonly _N_b_2824 _b;
    private static readonly ReadOnlyCollection<string> _N_field_list_3205;

    // Methods
    static _N_Anonymous_<a>_<b>();
    public _N_Anonymous_<a>_<b>(_N_a_2825 a, _N_b_2824 b);
    private bool Equals(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> other);
    public override bool Equals(object other);
    private object GetContent();
    private ReadOnlyCollection<string> GetFields();
    public override int GetHashCode();
    public static bool operator ==(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> a, _N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> b);
    public static bool operator ==(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> a, IAnonymous b);
    public static bool operator ==(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> a, object b);
    public static bool operator ==(IAnonymous a, _N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> b);
    public static bool operator ==(object a, _N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> b);
    public static explicit operator Tuple<_N_a_2825, _N_b_2824>(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> obj);
    public static bool operator !=(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> a, _N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> b);
    public static bool operator !=(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> a, IAnonymous b);
    public static bool operator !=(_N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> a, object b);
    public static bool operator !=(IAnonymous a, _N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> b);
    public static bool operator !=(object a, _N_Anonymous_<a>_<b><_N_a_2825, _N_b_2824> b);
    public override string ToString();
    public Tuple<_N_a_2825, _N_b_2824> ToTuple();

    // Properties
    public _N_a_2825 a { [DebuggerStepThrough] get; }
    public _N_b_2824 b { [DebuggerStepThrough] get; }
    private object this[string field] { get; }
    private object this[string field] { get; }
}
/* иЗвиНите зА неРовнЫй поЧерК */
Re[4]: Анонимные классы
От: hardcase Пират http://nemerle.org
Дата: 01.02.10 16:09
Оценка:
Здравствуйте, VladD2, Вы писали:


VD>Я не знаю как происходит сопоставление, но компилятор знает, что для данного типа свойству "dbl" соответствует параметр "dbl" единственного конструктора.


Имена параметров анонимного типа записаны в параметрах-генериков.

VD>Никакой разметки я не нашел, так что склонен полагать, что шарп просто специальным образом рассматривает анонимные типы. Это позволяет писать код типа следующего:

VD>
VD>var res1 = ary.Select(x => new { x, dbl = x * x }).Select(y => y.dbl);
VD>

VD>Если в таком коде вместо анонимных типов использовать нормальные и попытаться скомпилировать этот код с использованием, скажем, Linq 2 SQL, то будет выдана ошибка, так как провайдер не будет знать как сопоставить параметры конструктора и свойства объекта. Для анонимных типов же компилятор C# вызывает специальный метод:
VD>
VD>static System.Linq.Expressions.Expression. NewExpression New(
VD>  ConstructorInfo constructor, 
VD>  IEnumerable<Expression> arguments, 
VD>  params MemberInfo[] members)
VD>

VD>где последним параметром передает список свойств соответствующих параметрам конструктора (описание которого передается первым параметром).

Надо подумать (т.е. ничего не понял).... В принципе могу сделать совершенный эквивалент C#, благо совсем не сложно.
/* иЗвиНите зА неРовнЫй поЧерК */
Re[5]: Анонимные классы
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.02.10 16:11
Оценка:
Здравствуйте, hardcase, Вы писали:

H>В моей реализации на Nemerle:...

H>Создается также единственный класс вида (кое что можно убрать, интерфейс IAnonymous реализован эксплицитно)...

Отлично!

Добавь только параметр 'Type="<Anonymous Type>' в кастом-атрибут DebuggerDisplay, как у анонимных типов шарпа.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: Анонимные классы
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.02.10 16:25
Оценка:
Здравствуйте, hardcase, Вы писали:

H>Имена параметров анонимного типа записаны в параметрах-генериков.


Может быть. А может все еще проще и они просто захардкодили случай использования анонимных типов.

H>Надо подумать (т.е. ничего не понял).... В принципе могу сделать совершенный эквивалент C#, благо совсем не сложно.


Я тут подумал. Видимо все же тут магия не в том как строится анонимный тип, а в том, что компилятор знает как с анонимным типом работать. Так что видимо придется затачивать реализацию линка под твой тип. Сам тип, соотвественно, нужно перенести в Nemerle.dll/Nemerle.Macros.dll.

В общем, как сочтешь, что реализация устоялась, перенеси ее в эти сборки (базовые типы в Nemerle.dll, а макросы в Nemerle.Macros.dll).
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Анонимные классы
От: hardcase Пират http://nemerle.org
Дата: 02.02.10 16:31
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>В общем, как сочтешь, что реализация устоялась, перенеси ее в эти сборки (базовые типы в Nemerle.dll, а макросы в Nemerle.Macros.dll).


Ты не мог бы кратко объяснить, как нужно писать юнит тесты для компилятора (positive/negative)?
Без них как-то стрёмно коммитить
/* иЗвиНите зА неРовнЫй поЧерК */
Re[7]: Создание тестов для компилятора и стандартной библиот
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.02.10 17:17
Оценка: 21 (1)
#Имя: FAQ.nemerle.CompilerTests
Здравствуйте, hardcase, Вы писали:

H>Ты не мог бы кратко объяснить, как нужно писать юнит тесты для компилятора (positive/negative)?

H>Без них как-то стрёмно коммитить

Там довольно просто. Погляди примеры и сам все поймешь:
http://nemerle.googlecode.com/svn/nemerle/trunk/ncc/testsuite/positive/
http://nemerle.googlecode.com/svn/nemerle/trunk/ncc/testsuite/negative/

Вот пример "позитивного" теста:
http://nemerle.googlecode.com/svn/nemerle/trunk/ncc/testsuite/positive/array-prop.n
module M {
  Main () : void {
    def x = array [1,2,3];
    when (x.Length == 3) 
      System.Console.WriteLine ("OK");
  }
}

/*
BEGIN-OUTPUT
OK
END-OUTPUT
*/


Смысл очень простой. То что код скомпилировался без ошибок, запустился и выдал ожидаемый консольный вывод является положительным утверждать (assert-ом).
Ожидаемый консольный вывод помещается в коментарий идущий в конце исходного файла между BEGIN-OUTPUT и END-OUTPUT.
Если нужно подключить какие-то ссылки на сборки (в том числе и макросыне) то добавляешь комментарий следующего вида:
// REFERENCE: ИмяСборки

Например, следующий тест использует макрос из внешней сборки:
http://nemerle.googlecode.com/svn/nemerle/trunk/ncc/testsuite/positive/bug-1166.n
// REFERENCE: bug-1166-lib

System.Console.WriteLine (testMacro ())

/*
BEGIN-OUTPUT
test4
END-OUTPUT
*/

Сборка (макросная, в данном случае) на которую производится ссылка так же собирается из отдельного исходника лежащего там же:
http://nemerle.googlecode.com/svn/nemerle/trunk/ncc/testsuite/positive/bug-1166-lib.n
// REFERENCE: Nemerle.Compiler

macro testMacro () {<[
  def x = 4;
  Nemerle.IO.sprint ("test$x")
]>}

Кстате, в ней производится ссылка на сборку компилятора. Так же можно ссылаться на сборки из состава дотнета.
Имена тестовым файлам принято давать мелкими буквами разделяя слова подчеркиванием. Если тест сделан для бага, то формат должен быть как выше приведенных тестах: bug-четырезначый-номер-бага-в-мантисе.n

В негативных тестах вместо ожидаемого консолькного вывода описываются ожидаемые сообщения компилятора. Например:
http://nemerle.googlecode.com/svn/nemerle/trunk/ncc/testsuite/negative/block2.n
module M {
  bar3 () : int
  {
    brk : { 
      def qfoo () {
        brk (42) // E: non local goto
      }
      qfoo();
      qfoo();
      1
    }
  }

  Main () : void
  {
  }
  
}


Если сообщение произошло, то все ОК. Иначе, тест не пройдет.
Варианты префиксов:
E: — сообщение об ошибке (error)
W: — предупреждение (warning)
H: — подсказка (hint)

За двоеточим должна идти подстрока которая попоставляется с сообщением. Это не просто строка, а регулярное выражение. Синтаксис донтеный. Например, чтобы распознать любой символ нужно поставить точку, а для распознавания любого количества символов ".*". Это удобно использовать если в сообщении есть нинамическая часть которая может меняться или изобилует разными символами которые в регулярных выражениях являются специальными (скобки, точки и т.п.). Строку не обязательно задавать целиком. Если текст из коментария сопоставится хотя бы с частью сообщения, то уже будет "зачет".
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.