[Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 23.09.06 13:51
Оценка:
Не так давно IT в Nemerle'овской рассылке спрашивал про оператор ??, который есть в Шарпе и напрочь отсутствует в Nemerle. Ну я и решлил для расзимнки написать его макросом. Оказалось не все так просто
Сперва, приведу соответствующий кусок C# спецификации:

14.12 The null coalescing operator
The ?? operator is called the null coalescing operator.
null-coalescing-expression:
conditional-or-expression
conditional-or-expression ?? null-coalescing-expression
A null coalescing expression of the form a ?? b requires a to be of a nullable type or reference type. If a is
non-null, the result of a ?? b is a; otherwise, the result is b. The operation evaluates b only if a is null.
The null coalescing operator is right-associative, meaning that operations are grouped from right to left.
[Example: An expression of the form a ?? b ?? c is evaluated as a ?? (b ?? c). In general terms, an
expression of the form E1 ?? E2 ?? ... ?? EN returns the first of the operands that is non-null, or null if all
operands are null. end example]
The type of the expression a ?? b depends on which implicit conversions are available between the types
of the operands. In order of preference, the type of a ?? b is A0, A, or B, where A is the type of a, B is the
type of b, and A0 is the type that results from removing the trailing ? modifier, if any, from A. Specifically,
a ?? b is processed as follows:
• If A is not a nullable type or a reference type, a compile-time error occurs.
• If A is a nullable type and an implicit conversion exists from b to A0, the result type is A0. At run-time, a
is first evaluated. If a is not null, a is unwrapped to type A0, and this becomes the result. Otherwise, b is
evaluated and converted to type A0, and this becomes the result.
• Otherwise, if an implicit conversion exists from b to A, the result type is A. At run-time, a is first
evaluated. If a is not null, a becomes the result. Otherwise, b is evaluated and converted to type A, and
this becomes the result.
• Otherwise, if an implicit conversion exists from A0 to B, the result type is B. At run-time, a is first
evaluated. If a is not null, a is unwrapped to type A0 (unless A and A0 are the same type) and converted
to type B, and this becomes the result. Otherwise, b is evaluated and becomes the result.
Otherwise, a and b are incompatible, and a compile-time error occurs.


Решил делать все по уму, а именно стараться неприкоснительно следовать спецификации. Итак, вот такой вот макрос получился:

using System.Console;

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

namespace Iae.Macro
{
  /** This module is used to simplify work with op_Implicit operator.

      This module is stateless.
   */
  module OpImplicitHelper
  {
    /**
     * Used to print all implicit operators of some type
     */
    public PrintAll(cl : MType.Class) : void
    {
      def members = cl.tycon.LookupMember("op_Implicit");
      foreach (m is IMethod in members)
        WriteLine(m);
    }

    /**
     * Used to determine has a `from' type implicit conversation 
     * to a `to' type or hasn't.
     */
    public HasImplicit(from : MType.Class, to : MType.Class) : bool
    {
      def members = to.tycon.LookupMember("op_Implicit") 
                    + from.tycon.LookupMember("op_Implicit");
      ret : 
      {
        foreach (m is IMethod in members)
        {
          def tyRet = m.ReturnType;
          def tyPar = m.GetParameters().Head.ty;
          when (tyRet.Equals(to) && tyPar.Equals(from))
            ret (true);
        }  
        false;
      }
    }
  }

  macro @printImplicit(expr)
  {
    def typer = Macros.ImplicitCTX ();
    def tx = typer.TypeExpr (expr);
    match (tx.Type.Hint) 
    {
      | Some (Class (_ti, _args) as cl) => 
          OpImplicitHelper.PrintAll(cl);
          <[]>
      | _ => 
          Message.FatalError ("PrintImplicit macro can not be used with such expression.");
    }
  }

  macro @test(expr)
  {
    <[ if ($expr != null) $expr.Value; else (CI () : int) ]>
  }

  macro @?? (exprA, exprB)
  {
    def typer = Macros.ImplicitCTX ();
    def tx = (typer.TypeExpr(exprA).Type.Hint, typer.TypeExpr(exprB).Type.Hint);
    def teA = typer.TypeExpr(exprA);
    def teB = typer.TypeExpr(exprB);
    def tyA = typer.TypeExpr(exprA).Type;
    def tyB = typer.TypeExpr(exprB).Type;

    def upCast = 
      (texpr, ty) => 
        TExpr.TypeConversion (texpr.loc, ty, texpr, ty, 
                              ConversionKind.UpCast());
    def _implicit = 
      (texpr, ty) => 
        TExpr.TypeConversion (texpr.loc, ty, texpr, ty, 
                              ConversionKind.Implicit());

    match (tx) 
    {
      | (Some(Class(tiA, [tyA0]) as clA), Some(Class(_, _) as clB)) 
        when tiA.IsValueType && tiA.FullName == "System.Nullable" =>

          def clA0 = tyA0 :> MType.Class;
          if (clA0.Equals(clB) 
              || OpImplicitHelper.HasImplicit(clB, clA0))
          {
            /**
             *  BUG: 'Nullable[T]' can not be converted to 'T', 
             *       but it should.
             *  Example:
             *    def inull : int? = 10
             *    // cannot convert System.Nullable[int] to int:
             *    def i : int = inull :> int
            **/
            def st = <[ 
              if ($exprA != null) 
                $exprA.Value
              else 
                $(upCast(teB, tyA0) : typed)
            ]>;
            WriteLine($"nullable: B -> A0: $st");
            st
          }
          else if (clA.Equals(clB) 
                   || OpImplicitHelper.HasImplicit(clB, clA))
          {
            /**
             *  BUG: 'Nullable[T1]' can not be converted to 
             *       'Nullable[T2]' when 'T1' has implicit 
             *       conversation to 'T2', but it should.
             *  C# Example: 
             *    int? i = 10;
             *    double? d = i;
            **/
            def st = <[ 
              if ($exprA != null) 
                $exprA 
              else 
                $(upCast(teB, tyA) : typed)
            ]>;
            WriteLine($"nullable: B -> A: $st");
            st
          }
          else if (OpImplicitHelper.HasImplicit(clA0, clB))
          {
            /**
             *  BUG: 'T1' can not be converted to 'Nullable[T2]' 
             *       when 'T1' has implicit conversation to 'T2', 
             *       but it should.
             *  C# Example: 
             *    int i = 10;
             *    double? d = 0.1;
             *    d = i;
            **/
            def st = <[ 
              if ($exprA != null) 
                $(upCast(typer.TypeExpr(<[ $exprA.Value ]>), tyB) : typed)
              else 
                $exprB
            ]>;
            WriteLine($"nullable: A0 -> B: $st");
            st
          }
          else
          {
            Message.FatalError ($"Operator `??' cannot be applied to "
                                "operands of type `$clA' and `$clB'");
          }

      | (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");

      | (Some(Class(_, _) as clA), Some(Class(_, _) as clB))
        when clA.Equals(clB) || OpImplicitHelper.HasImplicit(clB, clA) =>

          /**
           * ?BUG: This match case and next one can be replaced with single
           *       one, but followed by code doesn't compile. Is this a design
           *       decision or a bug? (C# compile such code with no problems)
           *  Example: 
           *    #pragma indent
           *    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 
          **/
          def st = <[ 
            if ($exprA != null) 
              $exprA 
            else 
              $(upCast(teB, tyA) : typed)
          ]>;
          WriteLine($"reference: B -> A: $st");
          st

      | (Some(Class(_, _) as clA), Some(Class(_, _) as clB))
        when OpImplicitHelper.HasImplicit(clA, clB) =>

          def st = <[ 
            if ($exprA != null) 
              $(upCast(teA, tyB) : typed)
            else 
              $exprB
          ]>;
          WriteLine($"reference: A -> B: $st");
          st

      | _ => 
          Message.FatalError ($"Operator `??' cannot be applied to "
                              "operands of type `$tyA' and `$tyB'");
    }
  }
}


В процессе написания понял, что в Nemerle работа с Nullable типами просто никакая. В коде макроса в комментах можно встретить баги Nullable типов, которые мне с ходу удалось обнаружить. Чуть позже все баги занесу в багтрэкер, но прежде хочу разобраться в ряде моментов, ибо есть стойкое ощущение, что чего-то я недопонимаю.

Итак, пример использования макроса, с комментариями проблем:
#pragma indent

using System.Console;
using Iae.Macro;


class C1
  public override ToString() : string
    "C1"
  public Test() : void
    WriteLine("C1::Test");

class C2
  public static @:(_ : C2) : C1
    C1()
  public override ToString() : string
    "C2"

class CI
  public static @:(_ : CI) : int
    2

def getType['t] (_ : 't)
  $"$(typeof('t))"


def c1 = null : C1
def c2 = C2()
def r1 = c1 ?? c2 // (***)compile-time message: "reference: B -> A: if (c1 != null) c1; else (c2 : C1)"
def c1 = C1()
def t1 = getType(r1)
WriteLine($"TEST REFERENCE B -> A (result):  $r1")
WriteLine($"TEST REFERENCE B -> A (type):  $t1")
// run-time messages:
// TEST REFERENCE B -> A (result):  C2
// TEST REFERENCE B -> A (type):  C1

def r2 = c2 ?? c1 // compile-time message: "reference: A -> B: if (c2 != null) (c2 : C1); else c1"
def t2 = getType(r2)
WriteLine($"TEST REFERENCE A -> B (result):  $r2")
WriteLine($"TEST REFERENCE A -> B (type):  $t2")
// run-time messages:
// TEST REFERENCE B -> A (result):  C2
// TEST REFERENCE B -> A (type):  C1

def r0 = if (c1 == null) (c2 : C1) else c1 // собственно, тоже самое, что и в (***)
def t0 = getType(r0)
WriteLine($"TEST REFERENCE B -> A (result):  $r0")
WriteLine($"TEST REFERENCE B -> A (type):  $t0")
// run-time messages: выводит то, что ожидается в предыдущих случаях
// TEST REFERENCE B -> A (result):  C1
// TEST REFERENCE B -> A (type):  C1



def inull_v : int? = 10
def inull_n : int? = null
def r3 = inull_v ?? 1 // compile-time message: "nullable: B -> A0: if (inull_v != null) inull_v.Value; else (1 : int)"
def t3 = getType(r3)
WriteLine($"TEST NULLABLE B -> A0 (result):  $r3")
WriteLine($"TEST NULLABLE B -> A0 (type):  $t3")
// run-time messages:
// TEST NULLABLE B -> A0 (result):  10
// TEST NULLABLE B -> A0 (type):  System.Int32

def r4 = inull_n ?? 1 // compile-time message: "nullable: B -> A0: if (inull_n != null) inull_v.Value; else (1 : int)"
def t4 = getType(r4)
WriteLine($"TEST NULLABLE B -> A0 (result):  $r4")
WriteLine($"TEST NULLABLE B -> A0 (type):  $t4")
// run-time messages:
// TEST NULLABLE B -> A0 (result):  1
// TEST NULLABLE B -> A0 (type):  System.Int32


def r5 = test(inull_n) // это макрос, см. выше
def t5 = getType(r5)
WriteLine($"TEST NULLABLE B -> A0 (result):  $r5")
WriteLine($"TEST NULLABLE B -> A0 (type):  $t5")
// run-time messages:
// TEST NULLABLE B -> A0 (result):  2
// TEST NULLABLE B -> A0 (type):  System.Int32

def r5 = inull_n ?? CI() // compile-time message: "nullable: B -> A0: if (inull_n != null) inull_n.Value; else (CI () : int)"
// Compile-time exception:
//test.n:59:21:59:23: debug: Internal compiler error, please report a bug to bugs.nemerle.org. You can try modifying program near this location.
//error: internal compiler error: assertion failed in file ncc\generation\ILEmitter.n, line 801:
//_N_AutoModule::Main: failed to convert non-value type CI to a value type int
// .............

def r5 = if (inull_n != null) inull_n.Value; else (CI () : int)
def t5 = getType(r5)
WriteLine($"TEST NULLABLE B -> A0 (result):  $r5")
WriteLine($"TEST NULLABLE B -> A0 (type):  $t5")
// run-time messages:
// TEST NULLABLE B -> A0 (result):  2
// TEST NULLABLE B -> A0 (type):  System.Int32


Вообщем сдается мне, что есть какие-то тонкости, о которых я пока не знаю... Может есть какие-то идеи, почему макрос отрабатывает не так как хотелось бы?
... << RSDN@Home 1.2.0 alpha rev. 655>>

30.01.07 18:10: Перенесено модератором из 'Декларативное программирование' — IT
Превратим окружающую нас среду в воскресенье.
Re: [Nemerle] The null coalescing operator
От: PhantomIvan  
Дата: 23.09.06 14:55
Оценка:
Здравствуйте, ie, Вы писали:

ie>В процессе написания понял, что в Nemerle работа с Nullable типами просто никакая.


это определенно связано с наличием в языке option['a]
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 23.09.06 15:15
Оценка:
Здравствуйте, PhantomIvan, Вы писали:

ie>>В процессе написания понял, что в Nemerle работа с Nullable типами просто никакая.

PI>это определенно связано с наличием в языке option['a]

Никакой связи не вижу...
... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Re[3]: [Nemerle] The null coalescing operator
От: PhantomIvan  
Дата: 23.09.06 17:13
Оценка:
Здравствуйте, ie, Вы писали:

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


ie>>>В процессе написания понял, что в Nemerle работа с Nullable типами просто никакая.

PI>>это определенно связано с наличием в языке option['a]

ie>Никакой связи не вижу...


превратить переменную в null — это все равно что показать, что в ней нет никакого осмысленного значения
в c# value-типы не имели возможности обратиться в null, и не имеют ее сейчас, т.к. реально при декларировании чего-то вроде int? создается объект класса вроде Nullable<int>
по идее, производительность, для которой собственно, и существуют value-типы, падает в результате применения фактически ссылочного типа вместо исходного value-типа

в немерле появляется класс option['a], который принимает 2 значения, аналогично nullable типу выше: Some(x) или None()
то есть это то же самое фактически, но семантика выражена более явно, при этом собственно null не используется, что дает возможность применять option для обычных reference-объектов

я пользовался и nullable-типами c#-а, и option из немерле
однако пользовался не много, и сказать что лучше, не могу
по-моему те же яйца, только в профиль

говорят, nullable-типы в c#-пе были нужны в связи с базаданными типами
и говорят также, что ado.net 2.0 реально их не использует

впрочем, мне все равно, что использовать из этих двух вещей — ведь по сути что там, что здесь это вариант по сути
а есть ли у option оверхед (синтаксический или перфоманс), — непонятно
может, со временем, увижу неудобства, и мое мнение изменится
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: [Nemerle] The null coalescing operator
От: PhantomIvan  
Дата: 23.09.06 17:20
Оценка:
PI>впрочем, мне все равно, что использовать из этих двух вещей — ведь по сути что там, что здесь это вариант по сути

все равно с точки зрения производительности
а иногда структуры становятся классами, и наоборот (рефакторинг типа)
и тогда, кажется, option более удобен, т.к. позволяет пустить всех под 1 гребенку
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 23.09.06 17:48
Оценка: +1
Здравствуйте, PhantomIvan, Вы писали:

PI>превратить переменную в null — это все равно что показать, что в ней нет никакого осмысленного значения


С одной стороны да, а с другой — наллаблы были введены как раз для унификации reference и value типов с точки зрения null-значений.

PI>в c# value-типы не имели возможности обратиться в null, и не имеют ее сейчас, т.к. реально при декларировании чего-то вроде int? создается объект класса вроде Nullable<int>

PI>по идее, производительность, для которой собственно, и существуют value-типы, падает в результате применения фактически ссылочного типа вместо исходного value-типа

Прошу обратить внимание на выделенное:
public struct Nullable<T> where T: struct

Так что о падении производительности из-за использовании ссылок я бы смелых заявлений делать не стал бы

PI>в немерле появляется класс option['a], который принимает 2 значения, аналогично nullable типу выше: Some(x) или None()

PI>то есть это то же самое фактически, но семантика выражена более явно, при этом собственно null не используется, что дает возможность применять option для обычных reference-объектов

Однако, повторюсь, наллаблы добавили для возможности работать со всеми объектами (в том числе и value-типов), как с объектами принимающими значение null.
Some же пришло в Nemerle из функционального мира, где значения null не существует. Это более высокий уровень абстракции.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Re[5]: [Nemerle] The null coalescing operator
От: PhantomIvan  
Дата: 23.09.06 20:24
Оценка:
Здравствуйте, ie, Вы писали:

...

согласен в принципе, но нужны ли эти ноллаблы особо?
это внатуре вопрос
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[6]: [Nemerle] The null coalescing operator
От: IT Россия linq2db.com
Дата: 23.09.06 20:31
Оценка:
Здравствуйте, PhantomIvan, Вы писали:

PI>согласен в принципе, но нужны ли эти ноллаблы особо?


Как один из языков .NET Nemerle должен поддерживать всё, что есть в CLR.

PI>это внатуре вопрос


Что-то твоя падонковщина уже начинает напрягать. Это внатуре предупреждение.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Re[7]: [Nemerle] The null coalescing operator
От: PhantomIvan  
Дата: 23.09.06 21:04
Оценка:
IT>Как один из языков .NET Nemerle должен поддерживать всё, что есть в CLR.

тоже в принципе согласен
но это дается ценой некоторой путаницы, к примеру
коллекции .net — коллекции nemerle
nullable — option
delegates — function values

что применять, где применять, это путает
тех, кто только принимается программировать на немерле

какая падонковщина, не выдумывай
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 24.09.06 21:44
Оценка:
Здравствуйте, ie, Вы писали:

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

Что до оператора ??, то больно сложно у тебя что-то выходит. Мне кажется ты перемудрил. Ничего осмысленного о реализации сказать не могу (не вникал).

Зато могу дать предложение. Хорошо бы чтобы этот оператор поддерживал бы и option[T].
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[8]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 24.09.06 21:44
Оценка:
Здравствуйте, PhantomIvan, Вы писали:

PI>тоже в принципе согласен

PI>но это дается ценой некоторой путаницы, к примеру
PI>коллекции .net — коллекции nemerle
PI>nullable — option
PI>delegates — function values

Делегаты обратно соместимы с функциональными объектами, так что все ОК.

Думаю, что и option можно сделать не отличимым в использовании от nullable.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: [Nemerle] The null coalescing operator
От: PhantomIvan  
Дата: 24.09.06 22:50
Оценка: +2
Здравствуйте, VladD2, Вы писали:

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


PI>>тоже в принципе согласен

PI>>но это дается ценой некоторой путаницы, к примеру
PI>>коллекции .net — коллекции nemerle
PI>>nullable — option
PI>>delegates — function values

VD>Делегаты обратно соместимы с функциональными объектами, так что все ОК.


VD>Думаю, что и option можно сделать не отличимым в использовании от nullable.


но путаница возникает все-таки
вот, у Ильи кажется заминка с применением делагатов вышла
я к тому, что если некоторые вещи удобней в немерле (например функции), чем в дотнете (делегаты), то нужны ли, по большому счету, эти чисто дотнетные вещи...
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[10]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 24.09.06 23:57
Оценка:
Здравствуйте, PhantomIvan, Вы писали:

PI>но путаница возникает все-таки

PI>вот, у Ильи кажется заминка с применением делагатов вышла
PI>я к тому, что если некоторые вещи удобней в немерле (например функции), чем в дотнете (делегаты), то нужны ли, по большому счету, эти чисто дотнетные вещи...

Конечно. Причем просто для совместимости с другими языками дотнета. Почему-то меня не покидает уверенность, что Немерле еще не скоро станет едиственным или хотя бы основным языком дотнета. А до тех пор он будет прекрасным средством реализации длл-лек которые будут использоваться из других языков. И тут Немерле требуется все умение, чтобы успешно прикидываться C#-ом для внешних клиентов (т.е. создавать публичный интерфейс сборок не отличимый от C# последней версии).

В общем, с точки зрения возможности создания публичных интерфейсов и возможности использования чужих интерфейсов Nemerle обязан быть эдаким C#++, ну, или C##. Собственно это и декларируется.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: [Nemerle] The null coalescing operator
От: FR  
Дата: 25.09.06 05:18
Оценка:
Здравствуйте, VladD2, Вы писали:

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


PI>>но путаница возникает все-таки

PI>>вот, у Ильи кажется заминка с применением делагатов вышла
PI>>я к тому, что если некоторые вещи удобней в немерле (например функции), чем в дотнете (делегаты), то нужны ли, по большому счету, эти чисто дотнетные вещи...

VD>Конечно. Причем просто для совместимости с другими языками дотнета. Почему-то меня не покидает уверенность, что Немерле еще не скоро станет едиственным или хотя бы основным языком дотнета. А до тех пор он будет прекрасным средством реализации длл-лек которые будут использоваться из других языков. И тут Немерле требуется все умение, чтобы успешно прикидываться C#-ом для внешних клиентов (т.е. создавать публичный интерфейс сборок не отличимый от C# последней версии).


Правильней было бы все что нужно для прикидывания C# выделить в отдельную сущность (скажем библиотеку макросов — враперов) а не портить язык.
Re[2]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 25.09.06 05:51
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Насколько я помню в рассылке упоминалось, что поддержка нулабл-типов ограниченная. Ребятам было просто влом терять на них время.

VD>Откровенно говоря я сам так ни разу ими и не воспользовался по делу. Один раз захотелось, но обломался, так как они несовместимы со ссылочными типами.

Ну это в принципе ладно, не поддерживаются и бог с ними. Кстати, классная задачка, для желающего разобраться с компилятором (ну или хотя бы с его частью). Возможно, попробую пофиксать, коль время будет. Что до пользования ими, то мне реально пригодились наллаблы лишь несколько раз, примерно в таких случаях:

    // без наллаблов:
    public bool GetValue(out int value)
    {
        if (надоВернутьЗначение)
        {
            value = 10;
            return true;
        }
        else
        {
            value = 0;
            return false;
        }
    }

        int z;
        if (GetValue(out z))
            // do something

    // с наллаблами:
    public int? GetValueN()
    {
        return надоВернутьЗначение ? 10 : (int?)null;
    }

        int? zn = GetValueN();
        if (zn != null)
            // do something


С наллаблами код становиться лаконичнее, хотя и без них обойтись можно.

VD>Что до оператора ??, то больно сложно у тебя что-то выходит. Мне кажется ты перемудрил. Ничего осмысленного о реализации сказать не могу (не вникал).


Дык, я тоже думал проще получится, а оказалось сам оператор достаточно служный и имеет ряд ньюансов. На самом деле все эти ньюансы касаются наллабл-типов. С референс-типами все сводится к банальному ?:, но и тут у Немерла проблемы:

C#:
class A {}

class B : A {}

class C
{
    public static implicit operator B(C c)
    {
        return new B();
    }
}

A a = new A();
B b = new B();
C c = new C();

B b1 = c ?? b;
A a1 = c ?? a;
B b2 = c == null ? b : c;
A a2 = a == null ? c : a;

Nemerle:
class A
  ;
    
class B : A
  ;

class C
  public static @:>(c : C) : B
    B();


def a = A();
def b = B();
def c = C();
// expected B-, got C in computation branch: common super type of types [B, C] is just `System.Object', 
// please upcast one of the types to `System.Object' if this is desired
def b2 = if (c == null) b else c;
// expected C-, got A in computation branch: common super type of types [C, A] is just `System.Object', 
// please upcast one of the types to `System.Object' if this is desired
def a2 = if (a == null) c else a;


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

VD>Зато могу дать предложение. Хорошо бы чтобы этот оператор поддерживал бы и option[T].


Это следующее, что я хотел сделать, как только он мало-мальски заработает


Если в кратце, то основная моя загвоздка описывается вот таким вот кодом:
макрос

  macro @??? (exprA, exprB)
  {
    def typer = Macros.ImplicitCTX ();
    def teB = typer.TypeExpr(exprB);
    def tyA = typer.TypeExpr(exprA).Type;
    def tyB = typer.TypeExpr(exprB).Type;
    def tx = (tyA.Hint, tyB.Hint);

    def upCast = 
      (texpr, ty) => 
        TExpr.TypeConversion (texpr.loc, ty, texpr, ty, 
                              ConversionKind.UpCast());

    match (tx) 
    {
      | (Some(Class(_, _)), Some(Class(_, _))) =>

          def st = <[ 
            if ($exprA != null) 
              $exprA 
            else 
              $(upCast(teB, tyA) : typed)
          ]>;
          WriteLine($"Generated expression: $st");
          st

      | _ => 
          Message.FatalError ($"Operator `???' cannot be applied to "
                              "operands of type `$tyA' and `$tyB'");
    }
  }

тестовый пример
class C1
  public override ToString() : string
    "C1"

class C2
  public static @:(_ : C2) : C1
    WriteLine("Implicit!")
        C1()
  public override ToString() : string
    "C2"

def c1 = null : C1
def c2 = C2()
def r = c1 ??? c2
WriteLine($"r: $r")


А непонимаю я собственно вот что: при компиляции тестового примера получаю ожидаемое сообщение:

Generated expression: if (c1 != null) c1; else (c2 : C1)

Если вместо c1 ??? c2, тупо написать этот самый сгенерированный if:
def r = if (c1 != null) c1; else (c2 : C1)
WriteLine($"r: $r")

то при выполнении получу то, что хочу:

Implicit!
r: C1

а если оставить ???, то при выполнении:

r: C2


Почему, вроде бы правильно сгенерированный if выполняется не правильно, я не понимаю. Причем, если глянуть рефлектором, то код для обоих случаев получается идентичный:
    C1 c1 = null;
    C2 c2 = new C2();
    C1 c3 = (c1 == null) ? ((C1) c2) : c1;
    StringBuilder builder1 = new StringBuilder();
    builder1.Append("r: ");
    builder1.Append(c3);
    Console.WriteLine(builder1.ToString());
    C1 c4 = (c1 == null) ? ((C1) c2) : c1;
    StringBuilder builder2 = new StringBuilder();
    builder2.Append("r: ");
    builder2.Append(c4);
    Console.WriteLine(builder2.ToString());


Однако, если глянуть il, то видны различия:
c1 ??? c2
      L_0008: ldloc.0 
      L_0009: ldnull 
      L_000a: ceq 
      L_000c: ldc.i4.0 
      L_000d: ceq 
      L_000f: ldc.i4.1 
      L_0010: bne.un L_001b
      L_0015: ldloc.0 
      L_0016: br L_001c
      L_001b: ldloc.1 
      L_001c: stloc.2 


if (c1 != null) c1; else (c2 : C1)
      L_0042: ldloc.0 
      L_0043: ldnull 
      L_0044: ceq 
      L_0046: ldc.i4.0 
      L_0047: ceq 
      L_0049: ldc.i4.1 
      L_004a: bne.un L_0055
      L_004f: ldloc.0 
      L_0050: br L_005b
      L_0055: ldloc.1 
      L_0056: call C1 C2::op_Implicit(C2)
      L_005b: stloc.s c4


Почему такие различия я ума не приложу.
Как можно посмотреть сгенеренный после выполнения макроса код? Т.е. финальный код, который будет скармливаться компилятору. В интеграции я видел, вы сделали такое раскрытие кода, но пока нет возможности скачать VSSDK, так что поглядеть в студии не могу.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[12]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 25.09.06 06:07
Оценка:
Здравствуйте, FR, Вы писали:

FR>Правильней было бы все что нужно для прикидывания C# выделить в отдельную сущность (скажем библиотеку макросов — враперов) а не портить язык.


В конечном счете, было бы правильно, если б к этому все пришло, но выносить все эти вещи, поддерживаемые рантаймом, в отдельную сущность достаточно большой объем работы. Да и вообще, с приводимыми тут похожестями:

PI>>коллекции .net — коллекции nemerle

PI>>nullable — option

не согласен:
— коллекции nemerle нисколько не пытаются заменить коллекции .net, а расширяют их
— nullable — это средство представления null-based value-типов, причем без пенальти, как с точки зрения использования памяти, так и с точки зрения быстродействия. option же имеет другое предназначение.

согласен только с этой:

PI>>delegates — function values
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[5]: [Nemerle] The null coalescing operator
От: ie Россия http://ziez.blogspot.com/
Дата: 25.09.06 06:11
Оценка:
Здравствуйте, PhantomIvan, Вы писали:

PI>>впрочем, мне все равно, что использовать из этих двух вещей — ведь по сути что там, что здесь это вариант по сути


PI>все равно с точки зрения производительности


Вот как раз с точки зрения производительности, иногда и слудет отказаться от option в пользу nullable типов.

PI>а иногда структуры становятся классами, и наоборот (рефакторинг типа)


Первый раз слышу такое определение боксинга/анбоксинга

PI>и тогда, кажется, option более удобен, т.к. позволяет пустить всех под 1 гребенку


Да он ИМХО почти всегда удобней, но всегда есть "но"
... << RSDN@Home 1.2.0 alpha rev. 0>>
Превратим окружающую нас среду в воскресенье.
Re[6]: [Nemerle] The null coalescing operator
От: PhantomIvan  
Дата: 25.09.06 07:00
Оценка:
PI>>а иногда структуры становятся классами, и наоборот (рефакторинг типа)

ie>Первый раз слышу такое определение боксинга/анбоксинга


не, я имел в виду рефакторинг "сделать из структуры класс", "сделать из класса структуру"
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[12]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 25.09.06 15:38
Оценка:
Здравствуйте, FR, Вы писали:

FR>Правильней было бы все что нужно для прикидывания C# выделить в отдельную сущность (скажем библиотеку макросов — враперов) а не портить язык.


И как можно, например, поддержку делегатов выделить в библиотеку?

А оператор ?? и так будет в библиотеке которую в общем-то можно не использовать.

Вот только это стандартная библиотека и без нее никто не будет воспринимать язык.

Nemerle вообще два. Один базовый. А другой полный, в библиотеке.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Nemerle] The null coalescing operator
От: VladD2 Российская Империя www.nemerle.org
Дата: 25.09.06 15:38
Оценка: +1
Здравствуйте, ie, Вы писали:

ie>Что до пользования ими, то мне реально пригодились наллаблы лишь несколько раз, примерно в таких случаях:


ie>
ie>    // с наллаблами:
ie>    public int? GetValueN()
ie>    {
ie>        return надоВернутьЗначение ? 10 : (int?)null;
ie>    }

ie>        int? zn = GetValueN();
ie>        if (zn != null)
ie>            // do something
ie>


ie>С наллаблами код становиться лаконичнее, хотя и без них обойтись можно.


Тебе повезло, что использовался конкретный тип — int. А вот мне нужно было сделать тоже самое, но для абстрактного T. И я обломался, так как при этмо я обязан был пометить T как вэлью-тип (struct), а для бибилотеки это неприемлемо.

VD>>Что до оператора ??, то больно сложно у тебя что-то выходит. Мне кажется ты перемудрил. Ничего осмысленного о реализации сказать не могу (не вникал).


ie>Дык, я тоже думал проще получится, а оказалось сам оператор достаточно служный и имеет ряд ньюансов. На самом деле все эти ньюансы касаются наллабл-типов. С референс-типами все сводится к банальному ?:,


Очень станно. У наллабл есть свойство HasValue которое сводит работу с ним к тому же ?:. Но только ?: в Nemerle нет, так что к if()/else.

ie>но и тут у Немерла проблемы:

...
Это скорее всего баг в компиляторе. У них вообще с неявнями приведениями типов проблемы. Хорошо что это большая редкость.

ie>Вот и не ясно, толи это ограничения матча, толи это баг. Я написал Москалю, посмотрим, что об этом скажет.


Разумно. Потом странслуруй куда нить. А вообще надо было писать в конфу.

ie> Так вот если это ограничение матча и фиксать они его не планируют, то мой макрос упростить никак нельзя, даже наоборот, он еще больше может усложниться.


Приведи чистый код демонструрующий это ограничени (без нулаблов, и компилируемый).

ie>Если в кратце, то основная моя загвоздка описывается вот таким вот кодом:

...
ie>А непонимаю я собственно вот что: при компиляции тестового примера получаю ожидаемое сообщение:
...

Незнаю. Вникать нет времени. Но тоже вижу что код явно какой-то слишком избыточный. В ральных условиях обработка option[] сводится к (псевдокод):
using System.Console;

module P
{
  public GetValueWithDefault[T](value : option[T], default : T) : T
  {
      match (value)
      {
          | Some(x) => x
          | None    => default
      }
  }
}

def a = Some(1) : option[int];
def b = None() : option[int];

WriteLine(P.GetValueWithDefault(a, 0));
WriteLine(P.GetValueWithDefault(b, 0));

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

С nullable все должно быть точно так же:
using System.Console;

module P
{
  public GetValueWithDefault[T](value : T?, default : T) : T
        where T: struct
  {
    if (value.HasValue) value.Value else default;
  }
}

def a = 1 : int?;
def b = null : int?;

WriteLine(P.GetValueWithDefault(a, 0));
WriteLine(P.GetValueWithDefault(b, 0));


Если этот код не компилируется (а это так, проверил), то нужно лать баг-репорт и ждать пока Камил или Москаль разберутся. Или самому поправить ошибки.
Аналогичный код на C# без проблем компилируется:
using System;

class P
{
    static T GetValueWithDefault<T>(T? value, T def)
        where T: struct

    {
        return value.HasValue ? value.Value : def;
    }

    static void Main(string[] args)
    {
        int? a = 1;
        int? b = null;

        Console.WriteLine(GetValueWithDefault(a, 0));
        Console.WriteLine(GetValueWithDefault(b, 0));
    }
}


В общем, не забивай голову и не трать зря время. Такие горы кода по такому примитивному случаю делать нельзя. Надо исправлять причины проблем, а не замазвать их проявления.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.