Re[5]: [Этюд, C#] Overload resolution
От: Andir Россия
Дата: 04.02.09 03:06
Оценка: 43 (1)
Здравствуйте, Блудов Павел, Вы писали:
A>>Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:
[code skipped]
БП>Тогда и это должно компилироваться, но не делает этого:
БП>
БП>static void Main()
БП>{
БП>    Bar(t => { t.GetType(); T.Foo(); } );
БП>    Bar(s => { s.GetType(); S.Foo(); } );
БП>}
БП>


Да, конечно, если разрешить неоднозначность (например указав тип переменной), то будет компилироваться. А в предыдущем случае, компилятор каким-то своим умом разрешает эту неоднозначность ("ум" в этом случае активный, а не статично описанный похоже).

БП>Впрочем, я неверное неудачно выразился. "Это более чем имена" — так пойдёт? Компилятор почему-то трактует данный код как

БП>
БП>Bar((T T) => { T.GetType(); T.Foo(); } );
БП>Bar((S S) => { S.GetType(); S.Foo(); } );
БП>

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

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

Так можно:
static void Main()
{
    var T = default(T);
    T.Foo(); // ОК
}

А так нельзя:
static void Main()
{
    var t = default(T);
    t.Foo(); // error CS0176: Member 'T.Foo()' cannot be accessed with an instance reference; qualify it with a type name instead
}

И так нельзя:
static void Main()
{
    var T = default(S);
    T.Foo(); // error CS0176: Member 'S.Foo()' cannot be accessed with an instance reference; qualify it with a type name instead
}


С Уважением, Andir!
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 04:58
Оценка:
Здравствуйте, nikov, Вы писали:

VD>>Ну, ты бы вряд ли создал бы этюд по этому поводу если бы код скомпилировался бы.

N>Я не понял, он у тебя не компилируется что ли?

Компилируется. Это так называемый сарказм.

VD>>И на мой взгляд — это натуральный баг.

N>Нет, здесь нет бага. Но объясню попозже. Может быть, кто-то все-таки сам разберется.

По-моему время уже пришло.

ЗЫ

На мой взгляд баг чистой воды. Не в компиляторе, так в стандарте.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:01
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

VD>>1. Имена параметров T и S перекрывают глобальные анологичные символы. При этом компилятор должен сообщить о том, что в T и S нет не статического члена Foo.

БП>Фишка в том, что это не имена. Что интересно, РеШарпер 4.5 предлагает заменить их на

А что же?

И на основании чего вдруг в одном случае это имя параметра, а в другом типа?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:08
Оценка: 1 (1)
Здравствуйте, Блудов Павел, Вы писали:

A>>Всё таки это имена, иначе вот это было бы неверно и не должно было бы компилироваться:

A>>
A>>static void Main()
A>>{
A>>    Bar(T => { T.GetType(); T.Foo(); } );
A>>    Bar(S => { S.GetType(); S.Foo(); } );
A>>}
A>>


БП>Тогда и это должно компилироваться, но не делает этого:

БП>
БП>static void Main()
БП>{
БП>    Bar(t => { t.GetType(); T.Foo(); } );
БП>    Bar(s => { s.GetType(); S.Foo(); } );
БП>}
БП>


А оно и скомпилируется если устранить неоднозначность:
using System;

class Program
{
    static void Main()
    {
        //Bar(T => { T.GetType(); T.Foo(); });
        //Bar(S => { S.GetType(); S.Foo(); });
        Bar(new Action<S>(t => { t.GetType(); T.Foo(); }));
        Bar(new Action<S>(s => { s.GetType(); S.Foo(); }));
    }
    static void Bar(Action<T> x) { Console.WriteLine("T"); }
    static void Bar(Action<S> x) { Console.WriteLine("S"); }
}

class T
{
    public static void Foo() { }
}

class S
{
    public static void Foo() { }
}


Короче, баг чистой воды.
Посему очень хочется послушать объяснение Никова которое чудесным образом изменит мое мнение.

БП>Впрочем, я неверное неудачно выразился. "Это более чем имена" — так пойдёт? \


Не, не пойдет. Более чем — это уже магия. А я, знаете ли, верю в фокусы, но не в магию.

БП>Компилятор почему-то трактует данный код как

БП>
БП>Bar((T T) => { T.GetType(); T.Foo(); } );
БП>Bar((S S) => { S.GetType(); S.Foo(); } );
БП>

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

А с какого рожна делаются такие макроподстановки? Компилятор телепат?

Самое разумное в данном случае было бы сообщить об ошибке, чтобы человек принял меры. А так имеется замазанная проблема которая когда-то обязательно выстрелит.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:12
Оценка:
Здравствуйте, Andir, Вы писали:

A>Не-е, я думаю это не трактовка имени переменной, а вывод типа так срабатывает. То есть вариант выбора типа, в данном случае, возможен и он единственный, точно также как:


A>Так можно:

A>
A>static void Main()
A>{
A>    var T = default(T);
A>    T.Foo(); // ОК
A>}
A>


Ага. Забавно.

Еще забавно, что вот такой код:
Bar(S => { S.GetType(); T.Foo(); });

выдает сообщение об ошибке:

error CS0121: The call is ambiguous between the following methods or properties: 'Program.Bar(System.Action<T>)' and 'Program.Bar(System.Action<S>)'

Так что это явно странная трактовка имени переменной.

На мой взгляд — это баг чистой воды. По крайне мере поступать так не логично, а значит вредно.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:18
Оценка:
Здравствуйте, Блудов Павел, Вы писали:

БП>А проблема, обсуждаемая в этой теме, как я понял, в том, что компилятор нарушает принцип "либо дудочка, либо кувшинчик".

БП>Причём из самых благих побуждений — он пытается максимально угодить потребителям.

Это банальный баг в компиляторе. Вывод типа параметра на основании его имени, имеющий здесь место — это бред сивой кобылы.

Приведенный Андиром пример четко демонстрирует ошибку.

В общем, на мой взгляд Никову давно пора перестать интриговать и объяснить свою позицию. Ведь он считает, что бага нет.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:23
Оценка: 1 (1)
Здравствуйте, koandrew, Вы писали:

S>>Может будут какие-то соображения, почему этот код компилируется, а главное, почему выводит T и S?


K>Не уверен точно, но второе видимо объясняется тем, что

K>Type inference occurs as part of the compile-time processing of a method invocation (§7.5.5.1) and takes place before the overload resolution step of the invocation.

(§7.4.2)


Здорово. И как компилятор выводит, что у параметра с именем T тип T? Это не вывод типов, а телепатия.
По фигу когда делается вывод типов. Важно то, что он делается не верно.

Вот тебе вариации на эту тему:
Bar(S => { S.GetType(); T.Foo(); }); // error CS0121: The call is ambiguous between the following methods or properties: 'Program.Bar(System.Action<T>)' and 'Program.Bar(System.Action<S>)'
Bar(new Action<S>(T => { T.GetType(); T.Foo(); })); // error CS0176: Member 'S.Foo()' cannot be accessed with an instance reference; qualify it with a type name instead
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:27
Оценка: +1
Здравствуйте, koandrew, Вы писали:

K>Попробую пояснить, что я имею в виду. При выводе типа аргумента компилятор смотрит на использование методов объекта внутри лямбды. В нашем случае он видит использование метода типа T в первом случае и типа S во втором. Поскольку на этом этапе компиляции для него статические и экземплярные методы суть одно и то же, он выводит тип Action<T> для первой лямбды и Action<S> для второй. Дальше подключается разрешение перегрузок, и соответственно вызываются методы Bar, а также компилятор соображает, что в лямбдах речь идёт о статических методах, но это уже ничего не меняет, т.к. этап выведения типов уже завершён.


K>Надеюсь, я понятно изъяснил свои мысли...


Понятно только одно. Ты вообще не понял, что тут происходит.
Попробуй обдумать простую мысль. T и S в приведенном коде — это не типы, а имена параметров лябд. Таким образом обращение к ним через точку должно приводить к поиску экзеплярных методов с именем Foo. Но таковых нет!
Компилятор просто не имеет право делать предположений о типах параметров. Он или должен вывести их из инициализации, или сообщить о неоднозначности.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[5]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 04.02.09 05:36
Оценка: +1
Здравствуйте, nikov, Вы писали:

N>Про 7.5.4.1 замечено верно.


ОК.
Все что сказано в 7.5.4.1 — это то, что если свойство, параметр и т.п. перкрывают тип, то все равно можно обратиться к его статическим членам через имя типа.
Но на каком основании делается вывод о том, что у параметра T тип T, а у параметра S тип S?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: [Этюд, C#] Overload resolution
От: Sinix  
Дата: 04.02.09 06:07
Оценка:
Здравствуйте, nikov, Вы писали:

N>Скомпилируется ли этот код?


N>
N>using System;

N>class Program
N>{
N>    static void Main()
N>    {
N>        Bar(T => T.Foo());
N>        Bar(S => S.Foo());
N>    }
N>    static void Bar(Action<T> x) { Console.WriteLine("T"); }
N>    static void Bar(Action<S> x) { Console.WriteLine("S"); }
N>}

N>class T
N>{
N>    public static void Foo() { }
N>}

N>class S
N>{
N>    public static void Foo() { }
N>}
N>



Не должон — компилятору неоткуда выгрести тип переменной ибо вызывается неизвестно что. Насколько помню синтаксис, static метод не может дёргаться через инстанс. Вот если бы класс был бы статик — тогда точно нет.

Что упустил?
Re: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 04.02.09 08:05
Оценка: 69 (4) +1 -1
Здравствуйте, nikov, Вы писали:

Пришло время подытожить почему данный код компилируется и является корректным с точки зрения спецификации.
Т.к. случаи Bar(T => T.Foo()); и Bar(S => S.Foo()); аналогичны достаточно рассмотреть только первый случай.

Для вызова функции Bar(T => T.Foo()); производится поиск вызываемого метода.
Находятся два кандидата static void Bar(Action<T> x) и static void Bar(Action<S> x)

Далее, согласно алгоритму overload resolution производится проверка какой из двух методов является подходящим (applicable). Для этого делается проверка может ли лямбда T => T.Foo() быть преобразована к делегату Action<T> и Action<S> соответственно (раздел 6.5). Проверка осуществляется как "условная компиляция" выражения T => T.Foo() в заданом контексте.

Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.

Преобразование лямбды к типу Action<S> недопустимо. Для переменной T выводится что она имеет тип S. Вызов T.Foo() ошибочен, т.к. вызов статического метода недопустим через экземпляр класса. Соответственно T => T.Foo() не является valid expression в данном контексте.
Re[2]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 04.02.09 08:47
Оценка: -1
Здравствуйте, Алексей., Вы писали:

А>Пришло время подытожить почему данный код компилируется и является корректным с точки зрения спецификации.

А>[skipped]

Все абсолютно верно!
Re[6]: [Этюд, C#] Overload resolution
От: Блудов Павел Россия  
Дата: 04.02.09 10:03
Оценка:
Здравствуйте, Andir, Вы писали:
A>Не-е, я думаю это не трактовка имени переменной, а вывод типа так срабатывает.

Точно.
static void Main()
{
    Bar((T T) => { T.GetType(); T.Foo(); });
    Bar((T S) => { S.GetType(); S.Foo(); });
    Bar((S T) => { T.GetType(); T.Foo(); });
    Bar((S S) => { S.GetType(); S.Foo(); });
}

Два средних компилироваться отказываются.
... << RSDN@Home 1.2.0 alpha 4 rev. 1136>>
Re[2]: [Этюд, C#] Overload resolution
От: Mr.Cat  
Дата: 04.02.09 10:50
Оценка: +1
Здравствуйте, Алексей., Вы писали:

А>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.


А почему этого не происходит во втором случае?

А>Преобразование лямбды к типу Action<S> недопустимо. Для переменной T выводится что она имеет тип S. Вызов T.Foo() ошибочен, т.к. вызов статического метода недопустим через экземпляр класса. Соответственно T => T.Foo() не является valid expression в данном контексте.
Re[3]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 04.02.09 11:30
Оценка: 6 (1)
Здравствуйте, Mr.Cat, Вы писали:

MC>Здравствуйте, Алексей., Вы писали:


А>>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.


MC>А почему этого не происходит во втором случае?


А ты посмотрел в 7.5.4.1?

7.5.4.1 Identical simple names and type names
In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.


Во втором случае имя параметра и типа несогласованы, поэтому доступ к статическому члену недопустим, что приводит к отсутствию неявного преобразования от лямбды к делегату.
Re[4]: [Этюд, C#] Overload resolution
От: Mr.Cat  
Дата: 04.02.09 11:48
Оценка:
Здравствуйте, nikov, Вы писали:
N>Во втором случае имя параметра и типа несогласованы, поэтому доступ к статическому члену недопустим, что приводит к отсутствию неявного преобразования от лямбды к делегату.

Спасибо, не сразу заметил, что выделенное важно.
Re[3]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 04.02.09 12:03
Оценка: 33 (3)
Здравствуйте, Алексей., Вы писали:

А>Правильно ли я понимаю что это просто "компиляция" в контексте? Если выражение или блок успешно "скомпилировались" то выражение считается валидным, если неуспешно — невалидным?


Кстати, интересно, что если лямбда содержит вложенные лямбды, то это процесс может вызываться рекурсивно. Я, кажется, когда-то приводил пример, когда это вызывает комбинаторный взрыв:

using System;

class Program
{
    static void Main()
    {
        Foo(a => Foo(b => Foo(c => Foo(d => Foo(e => Foo(f => Foo(g => Foo(h => a))))))));
    }

    static string Foo(Func<byte, byte> f) { return null; }
    static string Foo(Func<short, short> f) { return null; }
    static string Foo(Func<int, int> f) { return null; }
    static string Foo(Func<long, long> f) { return null; }
    static string Foo(Func<string, string> f) { return null; }
}
Re[4]: [Этюд, C#] Overload resolution
От: Алексей.  
Дата: 04.02.09 12:10
Оценка:
Здравствуйте, nikov, Вы писали:

Угу, где-то в блоках разработчиков говорилось о том что вывод типов является NP-полной задачей. За пример, спасибо, не думал что так просто отправить компилятор в спячку.
Re[2]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 05:50
Оценка:
Здравствуйте, Алексей., Вы писали:

А>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T.


Это логическая ошибка. В указаном пункте нигде не сказано, что допускается интерпретировать имя переменной как ее тип.
Что же сказано в этом пункте? А вот что:

In a member access of the form E.I, if E is a single identifier, and if the meaning of E as a simple-name (§7.5.2) is a constant, field, property, local variable, or parameter with the same type as the meaning of E as a type-name (§3.8), then both possible meanings of E are permitted. The two possible meanings of E.I are never ambiguous, since I must necessarily be a member of the type E in both cases. In other words, the rule simply permits access to the static members and nested types of E where a compile-time error would otherwise have occurred.

Подчеркиваю! Тип Е должен быть таким же как тип Е. Другими словами мы должны иметь переменную или свойство имя которой совпадает с именем ее типа. Только при этом условии этот пункт должен срабатывать.
Сделано это чтобы имена переменных и свойств совпадающие с именами типов не скрывали статических членов этого типа.

В данном же случае имеет место неверное предположение о том, что тип параметра с именх Е тоже Е. Но это не верно! Это просто не откуда взять.

Так что компилятор Шарпа ведет себя не в соответствии с этим пунктом, а иначе. Он просто считает, что раз мы можем типизировать тело считая тип параметра равным Е, то значит все ОК. Но это не верное предположение. Программист мог попросту ошибиться.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 05.02.09 05:53
Оценка: +2
Здравствуйте, Mr.Cat, Вы писали:

MC>Здравствуйте, Алексей., Вы писали:


А>>Преобразование лямбды к типу Action<T> допустимо в данном контексте. Для переменной T выводится что она имеет тип T. Вызов T.Foo() согласно 7.5.4.1 является допустимым, т.к. T интерпретируется как имя класса T, а не переменной T. "Условная компиляция" происходит без ошибок. Соответственно T => T.Foo() является valid expression в данном контексте.


MC>А почему этого не происходит во втором случае?


Потому-что это не верные выводы. На самом деле T и S всегда интерпретируются как типы. Только в первом случае тип скрыт одноименной переменной. Далее на основании ошибочной интерпретации пункта 7.5.4.1 делается предположение, что параметр T имеет тип Т, что в корне не верно.

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