Re: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 27.09.06 09:26
Оценка: 30 (2)
Ну что ж. В итоге после копания в компиляторе, разговоров с Москалем и крепких размышлений, пришел вот к чему:

Для написания оператора '??' одним if/else не обойтись. Ну второй if/else спецефичный для наллаблов, особого труда не представляет, так что я действительно понаворотил кучу ненужного кода, который по сути является заплатками недоработок компилятора.

Для нормального функционирования макроса нужно пофиксать 2 бага:

1. Хотя этот баг Москаль считает не багом, а скорее некоторым ограничением, он все равно планирует его поправить:
    class A {}
    class B
    public static @:(_ : B) : A
        A()
    def a = A()
    def b = B()
    // expected B-, got A in computation branch:
    def c = if (a==null) b else a

В if/match проверяется только возможность приведения к базовому типу с целью поиска общего типа, но не проверяется возможность implicit приведения.

2. Баг с implicit/explicit приведением generic типов:
def p = (10 : int?) :> int; 
// это был пример с наллаблами, но проблема распростроняется и на все другие типы, например:
class Y['t]
  mutable _t : 't
  public this(t : 't)
    _t = t
  public static @:> (y : Y['t]) : 't
    y._t
  
def y = Y.[int](10)
def x = y :> int

С этой проблемой я решил попробовать разобраться сам. Сразу скажу ничерта не вышло, но в основной задаче, которую я себе ставил — понять как работает компилятор Немерла — маленько продвинулся.
В классе Typer есть метод TypeConversion. Который проверяет можно ли expression привести к некоторому типу. Так вот что этот метод делает касательно приведенного выше примера:
а. получает список всех implicit и explicit операторов у типа expression и приводимого типа;
б. выкидывает повторяющиеся;
в. строит needed — тип функции, с сигнатурой требуемой для нашего преобразования (Y[int] -> int);
г. проверяет имеют ли implicit/explicit операторы сигнатуру эквивалентную needed, если нет, выкидываем из рассмотрения;
д. выбираем наилучший оператор, используем его.
Так вот проблема возникла в пункте г. op_Explicit для приведенного примера имеет сигнатуру Y['t] -> 't, а не как нам бы хотелось Y[int] -> int. Я написал патч строящий правильные сигнатуры (в мантисе в баге я этот патч приаттачил), но это не решило проблему. Начал валиться с исключением Typer4:

tv 't.836 defined in Y and accessed from _N_AutoModule

Причина в принципе ясна, а вот как ее пофиксать пока не предумал. Вообщем решил запостить эту багу. Будет время, продолжу ознакомнение с компилятором и пофиксаю какую-нибудь другую багу в следующий раз

Хмммм...... Дописал все и думаю, а нафига я все это пишу....
Ну уж коль написал пущай будет. Мож кому интересно...
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 29.09.06 11:00
Оценка: 87 (1)
Финальный вариант макроса, забил на несколько экзотических правил C#-ового оператора и добавил поддержку option:

using Nemerle;
using Nemerle.Compiler;
using Nemerle.Compiler.Parsetree;
using Nemerle.Compiler.Typedtree;
using Nemerle.Text;

namespace Nemerle.Core
{
  macro @?? (exprA, exprB) 
  {
    def refEq = System.Type.ReferenceEquals;
    def ref3Eq = (t1,t2,t3) => refEq(t1, t2) || refEq(t1, t3);
    def tnullable = typeof (System.Nullable[_]);
    def toptNone = typeof (Nemerle.Core.option[_].None);
    def toptSome = typeof (Nemerle.Core.option[_].Some);

    def typer = Macros.ImplicitCTX ();
    def hA = typer.TypeExpr (exprA).Type.Hint;
    def hB = typer.TypeExpr (exprB).Type.Hint;
    def tx = (hA, hB);

    match (tx) {
      | (Some (Class (tiA, _)), Some (Class (tiB, _))) 
        when tiA.IsValueType && refEq (tiA.SystemType, tnullable)
                && !refEq (tiB.SystemType, tnullable)
             || ref3Eq (tiA.SystemType, toptNone, toptSome)
                && !ref3Eq (tiB.SystemType, toptNone, toptSome) =>

          <[ if ($exprA.HasValue) $exprA.Value else $exprB ]>;

      | (Some (Class (tiA, _)), Some (Class (tiB, _))) 
        when tiA.IsValueType && refEq (tiA.SystemType, tnullable)
                && refEq (tiB.SystemType, tnullable) =>

          <[ if ($exprA != null) $exprA else $exprB ]>;

      | (Some (Class (tiA, _)), Some (Class (tiB, _))) 
        when ref3Eq (tiA.SystemType, toptNone, toptSome)
                && ref3Eq (tiB.SystemType, toptNone, toptSome) =>

          <[ if ($exprA.IsSome) $exprA else $exprB ]>;

      | (Some (Class (tiA, _)), Some (Class (_, _))) 
        when tiA.IsValueType =>

          Message.FatalError (exprA.Location, 
                              $"`$tiA' is not a reference or nullable type "
                              "as required by the `??' operator");

      | _ => 
          <[ if ($exprA != null) $exprA else $exprB ]>;
    }
  }
}


Тесты:
using System.Console;



def GetExprType['t] (_ : 't)
{
  typeof ('t)
}



def rn = null;
def r1 = "str1";
def r2 = "str2";

assert (rn ?? r2 == "str2");
assert (GetExprType(rn ?? r2).Equals (typeof (string)));
WriteLine($"$(rn ?? r2)");

assert (r1 ?? r2 == "str1");
assert (GetExprType(r1 ?? r2).Equals (typeof (string)));
WriteLine($"$(r1 ?? r2)");



def dn : double? = null;
def d1 : double? = 0.1;
def d2 : double? = 0.2;

assert ((dn ?? d2).Value == 0.2);
assert (GetExprType(dn ?? d2).Equals (typeof (double?)));
WriteLine($"$(dn ?? d2)");

assert ((d1 ?? d2).Value == 0.1);
assert (GetExprType(d1 ?? d2).Equals (typeof (double?)));
WriteLine($"$(d1 ?? d2)");

assert (dn ?? 0.3 == 0.3);
assert (GetExprType(dn ?? 0.3).Equals (typeof (double)));
WriteLine($"$(dn ?? 0.3)");

assert (d1 ?? 0.3 == 0.1);
assert (GetExprType(d1 ?? 0.3).Equals (typeof (double)));
WriteLine($"$(d1 ?? 0.3)");



def on = None();
def on2 = None();
def o1 = Some(1);
def o2 = Some(2);

assert ((on ?? o2).IsSome);
assert ((on ?? o2).Value == 2);
assert (GetExprType(on ?? o2).Equals (typeof (option[int])));
WriteLine($"$(on ?? o2)");

assert ((o1 ?? o2).Value == 1);
assert (GetExprType(o1 ?? o2).Equals (typeof (option[int].Some)));
WriteLine($"$(o1 ?? o2)");

assert (on ?? 3 == 3);
assert (GetExprType(on ?? 3).Equals (typeof (int)));
WriteLine($"$(on ?? 3)");

assert (o1 ?? 3 == 1);
assert (GetExprType(o1 ?? 3).Equals (typeof (int)));
WriteLine($"$(o1 ?? 3)");

assert ((on ?? on2).IsNone);
assert (GetExprType(on ?? on2).Equals (typeof (option[int].None)));
WriteLine($"$(on ?? on2)");
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[2]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 29.09.06 17:44
Оценка:
Здравствуйте, ie, Вы писали:

ie>Финальный вариант макроса, забил на несколько экзотических правил C#-ового оператора и добавил поддержку option:


ie>
ie>    def tnullable = typeof (System.Nullable[_]);
ie>    def toptNone = typeof (Nemerle.Core.option[_].None);
ie>    def toptSome = typeof (Nemerle.Core.option[_].Some);
ie>


Вот это дело нужно в статические переменные отдельного скрытого модуля вынести. Не фига их при каждой компиляции создавать.

А эти:
ie> def refEq = System.Type.ReferenceEquals;
ie> def ref3Eq = (t1,t2,t3) => refEq(t1, t2) || refEq(t1, t3);
Лучше переписать на обычных локальных методах чтобы вызова по ссыне не блыло. Хотя это уже не так важно.

Так как оператор вызвается довольно часто стоит немного подоптимизировать.

И вот это:
          <[ if ($exprA.IsSome) $exprA else $exprB ]>;

мне кажется ошибкой. Ведь $exprA будет тоже типа Some(...), а это не верно. Надо так:
          <[ match ($exprA) { | Some(x) => x | _ => $exprB ]>;


Ну, и соотвественно тесты надо исправить. А то ты их явно под ошибку подстрогал.
Не:
assert (GetExprType(on ?? o2).Equals (typeof (option[int])));

а:
assert (GetExprType(on ?? o2).Equals (typeof (int)));



Потом match по двум значениям (кортежу) медленее чем по одному значению. А так как у тебя в первом элементе всегда Some (Class (tiA, _)), то стоит использовать вложенный match. Оно и понятнее будет.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 29.09.06 17:44
Оценка:
Здравствуйте, ie, Вы писали:

ie>
ie>    def tnullable = typeof (System.Nullable[_]);
ie>    def toptNone = typeof (Nemerle.Core.option[_].None);
ie>    def toptSome = typeof (Nemerle.Core.option[_].Some);
...
ie>        when tiA.IsValueType && refEq (tiA.SystemType, tnullable)
ie>


Кстати, меня интересует вопрос... Ты вот сравниваешь дотнетные типы основываясь на том, что все нужные тебе типы всегда определены во вне. А как быть если нужно сравнить типы которые могут быть определены как во внешней сборке, так и в компилируемом проекте?

Может есть какой-то более универсальный способ?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 09:32
Оценка:
Здравствуйте, VladD2, Вы писали:

ie>>
ie>>    def tnullable = typeof (System.Nullable[_]);
ie>>    def toptNone = typeof (Nemerle.Core.option[_].None);
ie>>    def toptSome = typeof (Nemerle.Core.option[_].Some);
ie>>


VD>Вот это дело нужно в статические переменные отдельного скрытого модуля вынести. Не фига их при каждой компиляции создавать.


Это да. Добавлю нужные типы в \ncc\external\internaltypes.n

VD>И вот это:

VD>
VD>          <[ if ($exprA.IsSome) $exprA else $exprB ]>;
VD>

VD>мне кажется ошибкой. Ведь $exprA будет тоже типа Some(...), а это не верно. Надо так:
VD>
VD>          <[ match ($exprA) { | Some(x) => x | _ => $exprB ]>;
VD>


Тут я сделал по аналогии с nullable. A ?? B. Если A и B nullable, то результат тоже nullable, если A nullable, а B нет, то и результат является базой nullable. Ты привел кусок кода, где оба выражения option. В этом случае и результат option.

VD>Потом match по двум значениям (кортежу) медленее чем по одному значению. А так как у тебя в первом элементе всегда Some (Class (tiA, _)), то стоит использовать вложенный match. Оно и понятнее будет.


Ок.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[3]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 09:32
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Кстати, меня интересует вопрос... Ты вот сравниваешь дотнетные типы основываясь на том, что все нужные тебе типы всегда определены во вне. А как быть если нужно сравнить типы которые могут быть определены как во внешней сборке, так и в компилируемом проекте?


Что-то не понял вопроса. Или ты имеешь ввиду типы, которые могут появиться в компайл-тайме после выполнения какого-то макроса?
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[4]: [Nemerle] The null coalescing operator
От: IT Россия linq2db.com
Дата: 02.10.06 12:26
Оценка:
Здравствуйте, ie, Вы писали:

ie>Ок.


Теперь пора заняться '??='
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Re[5]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 12:37
Оценка:
Здравствуйте, IT, Вы писали:

ie>>Ок.

IT>Теперь пора заняться '??='

Можно так:
  macro @??= (exprA, exprB) 
  {
    <[ {$exprA = $exprA ?? $exprB; $exprA} ]>
  }

Только вот хз что с ним делать, какие-то он экзотические задачи решает
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[5]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 13:05
Оценка:
Здравствуйте, IT, Вы писали:

IT>Теперь пора заняться '??='


А правильно ли я понимаю, что он будет иметь поведение другое нежели '??'. Например:
def i : int? = 10;
def j = i ?? 2;

у j будет тип int, а в случае с '??=' такая ситуация недопустима.
Хмм. ну ок, накидаю, как свободная минутка появится, все вроде тривиально.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[6]: [Nemerle] The null coalescing operator
От: IT Россия linq2db.com
Дата: 02.10.06 13:54
Оценка:
Здравствуйте, ie, Вы писали:

ie>А правильно ли я понимаю, что он будет иметь поведение другое нежели '??'. Например:


Если выражать через ??, то поведение должно быть примерно такое:

mutable a;
def b = a ?? { a = c; a };
Если нам не помогут, то мы тоже никого не пощадим.
Re[6]: [Nemerle] The null coalescing operator
От: IT Россия linq2db.com
Дата: 02.10.06 13:55
Оценка: +1
Здравствуйте, ie, Вы писали:

ie>Только вот хз что с ним делать, какие-то он экзотические задачи решает


Нормальные задачи, например, отложенная инициализация свойст недефолтными значениями.
Если нам не помогут, то мы тоже никого не пощадим.
Re[4]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.10.06 14:07
Оценка:
Здравствуйте, ie, Вы писали:

VD>>И вот это:

VD>>
VD>>          <[ if ($exprA.IsSome) $exprA else $exprB ]>;
VD>>

VD>>мне кажется ошибкой. Ведь $exprA будет тоже типа Some(...), а это не верно. Надо так:
VD>>
VD>>          <[ match ($exprA) { | Some(x) => x | _ => $exprB ]>;
VD>>


ie>Тут я сделал по аналогии с nullable. A ?? B. Если A и B nullable, то результат тоже nullable, если A nullable, а B нет, то и результат является базой nullable. Ты привел кусок кода, где оба выражения option. В этом случае и результат option.


Но это бессмысленно! Даже больше. Это просто неразумно. Да и работать не может. Ведь не может же один и тот же оператор в разные моменты возвращать Nullable[T] и T?
В общем, это явный баг.

Весь смысл этого оператора в том, чтобы он возвращал значение засунутое в nullable тип или значение используемое по умолчанию.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.10.06 14:07
Оценка:
Здравствуйте, ie, Вы писали:

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


VD>>Кстати, меня интересует вопрос... Ты вот сравниваешь дотнетные типы основываясь на том, что все нужные тебе типы всегда определены во вне. А как быть если нужно сравнить типы которые могут быть определены как во внешней сборке, так и в компилируемом проекте?


ie>Что-то не понял вопроса. Или ты имеешь ввиду типы, которые могут появиться в компайл-тайме после выполнения какого-то макроса?


На самом деле я уже нашел вопрос. Сравнивать нужно не дотнетные типы (т.е. не System.Type), а Nemerle-овское TypeInfo. Немерловцы сами создали список часто используемых типов и заполняют его в начальной стадии компиляции. Правда туда можно поместить только внешние типы. А внутренние нужно "лукапить" перед использованием.

Но проблемы все же остаются. В рефлекшоне есть фунукции позволяющие узнать является ли тип подтипом другого типа, или реализует ли тип некоторый интерйесй. Похоже подобных функций в Немерле попросту нет. Так что их нужно еще написать. А жаль.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 14:47
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Но это бессмысленно! Даже больше. Это просто неразумно. Да и работать не может. Ведь не может же один и тот же оператор в разные моменты возвращать Nullable[T] и T?

VD>В общем, это явный баг.

VD>Весь смысл этого оператора в том, чтобы он возвращал значение засунутое в nullable тип или значение используемое по умолчанию.


Однока, спецификация C# так не считает, и в этом я с ней солидарен.
int? p = null;
int? q = null;
if (p ?? q == 0); // тут мне 0 нафиг не нужен
... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Re[7]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 14:52
Оценка: +1
Здравствуйте, IT, Вы писали:

ie>>А правильно ли я понимаю, что он будет иметь поведение другое нежели '??'. Например:

IT>Если выражать через ??, то поведение должно быть примерно такое:

IT>
IT>mutable a;
IT>def b = a ?? { a = c; a };
IT>


Сравнивая поведение, я имел ввиду немного другое.

mutable a : int? = null;
def b = a ?? 5; // тут тип int, а если тупо выражать ??= через ??, то получим бредовые ошибки.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Re[5]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 14:53
Оценка:
Здравствуйте, VladD2, Вы писали:

ie>>Что-то не понял вопроса. Или ты имеешь ввиду типы, которые могут появиться в компайл-тайме после выполнения какого-то макроса?


VD>На самом деле я уже нашел вопрос. Сравнивать нужно не дотнетные типы (т.е. не System.Type), а Nemerle-овское TypeInfo. Немерловцы сами создали список часто используемых типов и заполняют его в начальной стадии компиляции. Правда туда можно поместить только внешние типы. А внутренние нужно "лукапить" перед использованием.


Я собственно в итоге все на TypeInfo и поменял.

VD>Но проблемы все же остаются. В рефлекшоне есть фунукции позволяющие узнать является ли тип подтипом другого типа, или реализует ли тип некоторый интерйесй. Похоже подобных функций в Немерле попросту нет. Так что их нужно еще написать. А жаль.


Ну BaseType то есть, а значит как минимум IsSubclassOf или IsAssignableFrom достаточно просто реализуемы.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Re[8]: [Nemerle] The null coalescing operator
От: IT Россия linq2db.com
Дата: 02.10.06 14:54
Оценка:
Здравствуйте, ie, Вы писали:

ie>def b = a ?? 5; // тут тип int, а если тупо выражать ??= через ??, то получим бредовые ошибки.


Какие именно бредовые? Если я буду делать тоже самое без макроса, то ошибки будут не бредовые?
Если нам не помогут, то мы тоже никого не пощадим.
Re[9]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 15:11
Оценка:
Здравствуйте, IT, Вы писали:

ie>>def b = a ?? 5; // тут тип int, а если тупо выражать ??= через ??, то получим бредовые ошибки.

IT>Какие именно бредовые? Если я буду делать тоже самое без макроса, то ошибки будут не бредовые?

Я про ошибки компилятора:

Error 1 expected option[int-]-, got int in assigned value: common super type of types [option[int-], int] is just `System.Object', please upcast one of the types to `System.Object' if this is desired E:\Ivan\Projects\MyOwn Projects\Nemerle Projects\Macro\Cns.MacroLibrary.Test\Main.n 78 9 Cns.MacroLibrary.Test
Error 2 there is no member named `HasValue' in System.Object- with type ? E:\Ivan\Projects\MyOwn Projects\Nemerle Projects\Macro\Cns.MacroLibrary.Test\Main.n 78 9 Cns.MacroLibrary.Test
Error 3 there is no member named `Value' in System.Object- with type ? E:\Ivan\Projects\MyOwn Projects\Nemerle Projects\Macro\Cns.MacroLibrary.Test\Main.n 78 9 Cns.MacroLibrary.Test

А по уму бы написать:

`5' should have type `option[int]'.

... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Re[10]: [Nemerle] The null coalescing operator
От: IT Россия linq2db.com
Дата: 02.10.06 15:20
Оценка:
Здравствуйте, ie, Вы писали:

ie>>>def b = a ?? 5; // тут тип int, а если тупо выражать ??= через ??, то получим бредовые ошибки.

IT>>Какие именно бредовые? Если я буду делать тоже самое без макроса, то ошибки будут не бредовые?

ie>Я про ошибки компилятора:


И я про тоже.

ie>А по уму бы написать:

ie>

ie>`5' should have type `option[int]'.


Так было бы совсем хорошо. Но как я понимаю, компилятору всё равно делается ли что-то неверно из макроса или прямо в коде. Ошибки будут одни и те же.

Конечно, лучше выдавать вменяемые ошибки. А ещё лучше, сделать самому приведение, если это возможно.
Если нам не помогут, то мы тоже никого не пощадим.
Re[11]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 02.10.06 15:36
Оценка:
Здравствуйте, IT, Вы писали:

ie>>А по уму бы написать:

ie>>

ie>>`5' should have type `option[int]'.

IT>Так было бы совсем хорошо. Но как я понимаю, компилятору всё равно делается ли что-то неверно из макроса или прямо в коде. Ошибки будут одни и те же.

Если так написать:
def b = {when (a == null) a = 6; a};

, то хотя бы не будет сообщений про HasValue/Value. Хотя эти сообщение спецефично для option.

IT>Конечно, лучше выдавать вменяемые ошибки.


+100, а то эти невменяемые отмазки компилятора уже напрягают.

IT>А ещё лучше, сделать самому приведение, если это возможно.


Хмм. Какими правилами руководствоваться? Implicit и UpCast?
... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.