Здравствуйте, nikov, Вы писали:
N>А ты посмотрел в 7.5.4.1?
N>
N>7.5.4.1 Identical simple names and type names
N>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.
И что ты узрел в выделенном?
Где ты видишь в данном примере, что у параметра тип совпадает с именем? Его тип не определен! Его просто не откуда вывести! То что кто-то там пытается из этого имени вызвать что-то еще ничего не значит.
Вот тебе простой пример демонстрирующий что будет если тип переменной выведется по другому:
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
Что у нас изменилось? А только лишь тип параметра с именем "T".
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Понятно только одно. Ты вообще не понял, что тут происходит. VD>Попробуй обдумать простую мысль. T и S в приведенном коде — это не типы, а имена параметров лябд. Таким образом обращение к ним через точку должно приводить к поиску экзеплярных методов с именем Foo. Но таковых нет! VD>Компилятор просто не имеет право делать предположений о типах параметров. Он или должен вывести их из инициализации, или сообщить о неоднозначности.
Я сам нашёл опровержения моей теории, но поскольку nikov уже прокомментировал мой пост, не посчитал нужным что-то постить дополнительно...
Здравствуйте, Алексей., Вы писали:
А>Угу, где-то в блоках разработчиков говорилось о том что вывод типов является NP-полной задачей.
Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).
Вот при выводе типов из использования (как в Немерле) разрешение перегрузок и правда является NP-полной задачей. Но и там можно применять эвристики делающие процесс разрешения перегрузок приемлемо бысрым.
А>За пример, спасибо, не думал что так просто отправить компилятор в спячку.
Это потому, что в компиляторы такие алгоритмы используются. Тот же пример на Немерле компилируется в мгновение ока. И интеграция на нем работает отлично:
Здравствуйте, VladD2, Вы писали:
VD>Это логическая ошибка. В указаном пункте нигде не сказано, что допускается интерпретировать имя переменной как ее тип.
7.5.4.1 так прямо и говорит выражение simple-name (в данном случае 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.
VD>В данном же случае имеет место неверное предположение о том, что тип параметра с именх Е тоже Е. Но это не верно! Это просто не откуда взять.
Виноват, забыл подробно описать как же компилятор "выводит" что параметр лямбды T имеет тип T.
Цитирую раздел 6.5:
If D has a void return type and the body of F is an expression, when each parameter of F is given the type of the corresponding parameter in D, the body of F is a valid expression (wrt §7) that would be permitted as a statement-expression (§8.6).
Рассмотрим преобразование лямбды T => T.Foo() в делегат Action<T>.
Action определен как public delegate void Action<T>(T obj);
Вызов инстанцированного делегата будет выглядеть как: void (T obj), где T это имя класса T в примере nikov'а, а не тип-параметр делегата.
Декларацию лямбды можно записать как: void (? T)
Сопоставляя параметры делегата и параметры лямбды получаем что параметр T лямбды имеет тип T.
Здравствуйте, VladD2, Вы писали:
VD>Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).
Я так понимаю реализация 7.4.2.11 Inferred return type порождает комбинаторику аналогичную приведенную nikov'ым примером.
VD>Вот при выводе типов из использования (как в Немерле) разрешение перегрузок и правда является NP-полной задачей. Но и там можно применять эвристики делающие процесс разрешения перегрузок приемлемо бысрым.
Это значит что найти пример который отправит в спячку Nemerle несколько сложнее.
Здравствуйте, VladD2, Вы писали:
VD>Где ты видишь в данном примере, что у параметра тип совпадает с именем? Его тип не определен! Его просто не откуда вывести! То что кто-то там пытается из этого имени вызвать что-то еще ничего не значит.
Посмотри 6.5 Anonymous function conversions — там описано, как происходит преобразование от анонимной функции к делегатам, и откуда берутся типы параметров анонимной функции, если они не указаны явно.
An anonymous-method-expression or lambda-expression is classified as an anonymous function (§7.14). The expression does not have a type but can be implicitly converted to a compatible delegate type or expression tree type. Specifically, a delegate type D is compatible with an anonymous function F provided:
...
• If D has a void return type and the body of F is an expression, when each parameter of F is given the type of the corresponding parameter in D, the body of F is a valid expression (wrt §7) that would be permitted as a statement-expression (§8.6).
Как видишь, компилятору приходится заглядывать в тело анонимного метода, чтобы проверить, существует ли преобразование к делегату. Теперь, что происходит при обработке вызова метода Bar? Смотрим дальше:
7.5.5.1 Method invocations
• The set of candidate methods for the method invocation is constructed. For each method F associated with the method group M:
...
• F is applicable with respect to A (§7.4.3.1).
7.4.3.1 Applicable function member
A function member is said to be an applicable function member with respect to an argument list A when all of the following are true:
...
an implicit conversion (§6.1) exists from the argument to the type of the corresponding parameter
То есть, когда у тебя анонимный метод задан в качестве аргумента к перегруженному методу Bar, то компилятор перебирает методы и ищет те, к для которым существует неявное преобразование от анонимного метода к соответсвующему типу в сигнатуре. Поэтому производится несколько попыток задать типы (взятые из параметров делегата в сигнатуре) параметрам анонимного метода, и проверить, будет ли тело анонимного метода корректным. Те попытки, которые будут успешными, дадут кандидатов для overload resolution. В моем примере одна успешная попытка, там что overload resolution тривиален. Так что, тип есть откуда вывести.
Здравствуйте, VladD2, Вы писали:
VD>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.
Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.
Здравствуйте, VladD2, Вы писали:
А>>Угу, где-то в блоках разработчиков говорилось о том что вывод типов является NP-полной задачей.
VD>Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).
Как бы не так. Вывод из инициализации — это только для переменных, объявленных через var. Для параметров в лямбдах тип может выводиться из анализа использования этих параметров (куда они передаются, какие методы вызываются, какие операторы применяются, к чему они кастятся и т.д.). В этом случае строится дерево всевозможных вариантов и производится перебор. Я достаточно хорошо разбираюсь в этом процессе, и много обсуждал его и с его разработчиками в Mirosoft, и с теми, кто его реализовывал для ReSharper в JetBrains.
Я не знаю зачем ты мне все это излагал. Все это я и так знал.
N>То есть, когда у тебя анонимный метод задан в качестве аргумента к перегруженному методу Bar, то компилятор перебирает методы и ищет те, к для которым существует неявное преобразование от анонимного метода к соответсвующему типу в сигнатуре. Поэтому производится несколько попыток задать типы (взятые из параметров делегата в сигнатуре) параметрам анонимного метода, и проверить, будет ли тело анонимного метода корректным. Те попытки, которые будут успешными, дадут кандидатов для overload resolution. В моем примере одна успешная попытка, там что overload resolution тривиален. Так что, тип есть откуда вывести.
У тебя будет два подходящих кандидата. А поведение компилятора C# ошибка.
Повторение одних и тех же аргументов не делают эти аргументы правильнее.
Ты лучше объясни почему ты считаешь, что 7.5.4.1 должен накладывать какие-то ограничения на тип параметра?
Там же ясно сказано:
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. For example:
Так что же тебе дает основание пологать, что параметр T имеет тот же тип что и тип T?
ЗЫ
Ты по этому поводу с разработчиками компилятора говорил?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, nikov, Вы писали:
N>Здравствуйте, VladD2, Вы писали:
VD>>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее.
N>Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.
Я тебе уже 5 раз указал на ошибку в твоих рассуждениях. То что она совпала с работой компилятора может и случайность.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, VladD2, Вы писали:
VD>Ты лучше объясни почему ты считаешь, что 7.5.4.1 должен накладывать какие-то ограничения на тип параметра?
Потому что из 7.5.4.1 следует, что T.Foo() — это некорректное выражение, если T имеет тип, отличный от T, и корректное, если Т имеет тип Т.
VD>Там же ясно сказано: VD>
VD>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. For example:
VD>Так что же тебе дает основание пологать, что параметр T имеет тот же тип что и тип T?
1) Во время overload resolution, такое основание мне дает то, что в данный момент рассматривается overload с типом Action<T> в сигнатуре.
2) После overload resolution, такое основание мне дает то, что во время overload resolution был выбран overload с типом Action<T> в сигнатуре.
VD>ЗЫ VD>Ты по этому поводу с разработчиками компилятора говорил?
Здравствуйте, VladD2, Вы писали:
VD>Я тебе уже 5 раз указал на ошибку в твоих рассуждениях. То что она совпала с работой компилятора может и случайность.
Когда я говорю про overload resolution в C#, у меня не бывает ошибок
Я тебе уже несколько раз объяснил, почему то, что ты считаешь ошибкой — это не ошибка. Это просто неожиданный эффект.
Здравствуйте, nikov, Вы писали:
N>Скомпилируется ли этот код?
[code skipped]
Итак, поведение компилятора выглядит обоснованным. Но всё равно остаётся какое-то неуловимое ощущение общей нелогичности такого поведения.
Архитектурное решение: Вывод типа лямбды на основе перебора возможных вариантов.
К чему это приводит: нарушение инвариантности имени переменной (от её имени зависит "правильность" программы), невозможности рефакторинга "Rename" в некоторых случаях.
Вопрос: А есть ли примеры подобного нарушения в языке C#, не относящиеся к выводу типа в лямбдах?
Дополнительно: Считаешь ли ты подобный выбор разработчиков обоснованным?
P.S. Логичным продолжением развития такого поведения является очевидно распространение подхода на содержимое методов. Думаю будет много интересных эффектов
Здравствуйте, nikov, Вы писали:
VD>>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее. N>Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.
Ошибочное поведение внесенное в документацию не перестает быть ошибочным.
Здравствуйте, nikov, Вы писали:
N>Как бы не так. Вывод из инициализации — это только для переменных, объявленных через var. Для параметров в лямбдах тип может выводиться из анализа использования этих параметров (куда они передаются, какие методы вызываются, какие операторы применяются, к чему они кастятся и т.д.). В этом случае строится дерево всевозможных вариантов и производится перебор. Я достаточно хорошо разбираюсь в этом процессе, и много обсуждал его и с его разработчиками в Mirosoft, и с теми, кто его реализовывал для ReSharper в JetBrains.
Значит недостаточно. Для лямбд, в C#, тип параметров (если он не задан явно) всегда берется из места куда он подставляется. Это может быть параметр функции или переменная. Вывода типов из использования в C# нет. Так что тип возвращаемого значения можно вывести если известны все типы параметров лямбды. Но не зная хотя бы одного типа параметра компилятор будет бессилен.
Попробуй на досуге повторить на C# вот этот немерловый код:
def f1 = d => d.AddDays(1D);
WriteLine(f1(DateTime.Now));
def f2 = d => d.AddDays(1D);
_ = f2((d => d.AddDays(1D))(DateTime.Now));
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Алексей., Вы писали:
А>Здравствуйте, VladD2, Вы писали:
VD>>Это шарпа то? Гы-гы. Ты видимо читал блоги каких-то других программистов. В Шарпе линейный вывод типов (только из инициализации).
А>Я так понимаю реализация 7.4.2.11 Inferred return type порождает комбинаторику аналогичную приведенную nikov'ым примером.
Ошибочно полагаешь. К тому же не понимаю зачем гадать когда я тебе продемонстрировал, что тот же самый код другим компилятором компилируется молниеносно.
Не ужели этого не достаточно, чтобы понять, что в данном случае вопрос в качестве применяемых алгоритмов?
VD>>Вот при выводе типов из использования (как в Немерле) разрешение перегрузок и правда является NP-полной задачей. Но и там можно применять эвристики делающие процесс разрешения перегрузок приемлемо бысрым.
А>Это значит что найти пример который отправит в спячку Nemerle несколько сложнее.
Потенциально — конечно. Достаточно добиться перемножения в несколько миллионов вариантов. Но на прктике такого не встретишь. Приведенный же пример весьма реален.
Но мы то ведь о конкретном случае говорили?
Так что — это просто баг или не качественная реализация. Компилятор явно зацикливается. А с чего бы это может произойти?
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Undying, Вы писали:
VD>>>В общем, очередное доказательство, того, что люди с большим энтузиазмом ищут оправдание ошибки нежели признают ее. N>>Только все происходило наоборот: я прочитал спецификацию, понял, что она приводит к таком интересному эффекту, а только потом написал код и убедился, что он работает, как предписано спецификацией. Никаких ошибок нет.
U>Ошибочное поведение внесенное в документацию не перестает быть ошибочным.
А в документации как раз вроде как все ОК. Там же сказано, что все это относится для переменных тип и название которых совпадает с перекрытым типом.
В прочем, по сути я согласен.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Здравствуйте, Алексей., Вы писали:
А>Здравствуйте, VladD2, Вы писали:
VD>>Это логическая ошибка. В указаном пункте нигде не сказано, что допускается интерпретировать имя переменной как ее тип. А>7.5.4.1 так прямо и говорит выражение simple-name (в данном случае 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.
Упорством с которым вы игнорируете выделенный текст меня поражает. Я все же услышу комментарий по поводу выделенного мной текста:
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.
А>Декларацию лямбды можно записать как: void (? T)
А>Сопоставляя параметры делегата и параметры лямбды получаем что параметр T лямбды имеет тип T.
С чего бы это?
Все что можно с уверенностью утверждать, что выполняются следующие правила:
1. Параметр Т совпадает по имени с типом Т.
2. Тип параметра T неизвестен.
По 7.5.4.1 должно выполняться еще одно правило. Тип переменной должен иметь то же тип, что и перекрываемый.
Вот простой пример демонстрирующий идею:
using System;
class Program
{
public staticProgram Program;
static void TestStatic() { }
void Test() { }
static void Main(string[] args)
{
Program.TestStatic();
Program.Test();
}
}
Этот код отличо компилируется и работает.
Если мы теперь добавим еще один класс содержащий такой же экзеплярный метод и заменим тип переменной на него:
using System;
class A
{
public void Test() { }
}
class Program
{
public staticA Program;
static void TestStatic() { }
void Test() { }
static void Main(string[] args)
{
Program.TestStatic();
Program.Test();
}
}
То вызов статического метода сразу станет не верным, так как не выполнится правило "переменная должна иметь тот же тип, что и перекрываемый".
Возвращаясь к исходному примеру, совершенно ясно, что тип переменной T вывести нельзя (так как он не задается ни явно, ни опосредовано). Стало быть правило 7.5.4.1 применять нельзя и даже вызов статического метода будет ошибкой. А уж типизация лямбды на этом основании просто нонсенс.
В общем, вы защищаете банальны баг подсистемы вывода типов шарпа.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.