Re[5]: [Этюд, C#] Overload resolution
От: koandrew Канада http://thingselectronic.blogspot.ca/
Дата: 08.02.09 18:19
Оценка:
Здравствуйте, nikov, Вы писали:

N>Если там есть что-то непонятное, обращайся, постараюсь объяснить.


Не то чтобы непонятно что-то конкретное — просто в целом эта часть как-то мутно написана. Иногда приходится одно предложение по десять раз перечитывать, чтобы понять, о чём собственно речь. Но за предложение спасибо — я как раз запланировал глубокое изучение этой части, потому как твой этюд показал, что я нихрена в этой части не понимаю
[КУ] оккупировала армия.
Re[6]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 08.02.09 20:06
Оценка: 224 (8) +2 :)))
Здравствуйте, VladD2, Вы писали:

VD>Причем я пробовал применять два алгоритма и оба приводят к багу. Вот они:

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

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

Я готов обсудить вопросы неудачных решений в стандарте после того, как мы убедимся, что здесь нет ошибок реализации, т.е. поведение компилятора соответствует текущей версии стандарта. Нельзя сказать, что я в восторге от того, как overload resolution взаимодействует с лямбдами.

VD>Попробуй обдумать простую мысль. T и S в приведенном коде — это не типы, а имена параметров лябд. Таким образом обращение к ним через точку должно приводить к поиску экзеплярных методов с именем Foo. Но таковых нет!

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

VD>У тебя будет два подходящих кандидата. А поведение компилятора C# ошибка.

Почему ты считаешь, что они оба будут подходящими? Как я покажу ниже, это не так.

VD>И как компилятор выводит, что у параметра с именем T тип T? Это не вывод типов, а телепатия.

VD>Все что сказано в 7.5.4.1 — это то, что если свойство, параметр и т.п. перкрывают тип, то все равно можно обратиться к его статическим членам через имя типа.
VD>Но на каком основании делается вывод о том, что у параметра T тип T, а у параметра S тип S?

Как я понял, твое основное возражение заключается в том, что 7.5.4.1 не может применяться, потому что он требует, чтобы имя параметра совпадало с именем его типа, а в моем примере неизвестно, что параметр T имеет тип T. Это не так. Сейчас я продемонстрирую, почему в момент применения правила 7.5.4.1 известно, что параметр T имеет тип T. Попробуй на время отложить свою интуицию, воспитанную на немерле, и внимательно проследить по спецификации C#, что здесь происходит. Для удобства я пронумеровал шаги рассуждения. Если тебе не понятно, что происходит на некотором шаге, укажи его номер.

Компилятор видит это выражение:
Bar(T => T.Foo())

Сначала он определяет, что такое Bar. Это оказывается группа методов, состоящая из двух методов с сигнатурами Bar(Action<T>) и Bar(Action<S>). Значит, все выражение целиком — это method invocation. Алгоритм дальнейших действий описан в 7.5.5.1 Method invocations.
1) Первым делом, надо составить множество методов-кандидатов. Для этого надо перебрать все методы в группе методов Bar.
1.1) Рассматриваем метод Bar(Action<T>). Это не-generic метод, поэтому для добавления его во множество кандидатов должны выполняться следущие условия:
1.1.1) При вызове, у группы методов Bar должен отсутствовать список типов-аргументов. Условие выполняется.
1.1.2) Метод Bar(Action<T>) должен быть применим к списку аргументов (T => T.Foo()). Правила проверки применимости описаны в 7.4.3.1 Applicable function member. Так как метод не имеет модификатора params, то чтобы он был применим, должны выполняться следующие условия:
1.1.2.1) Количество аргументов должно совпадать с количеством параметров. Условие выполняется (1=1).
1.1.2.2) Если у параметра нет модификаторов ref/out, то их не должно быть и у аргумента. Условие выполняется.
1.1.2.3) Должно существовать неявное преобразование от аргумента T => T.Foo() к типу параметра Action<T>. В данном случае аргумент является лямбда-выражением, поэтому правила, определяющие наличие неявного преобразования, описаны в 6.5 Anonymous function conversions. Внимание! Проверяя наличие неявного преобразования, компилятор совершенно не заботится о том, что overload resolution для метода Bar еще не завершен, или что где-то есть еще метод с сигнатурой Bar(Action<S>). До него дело еще дойдет. Сейчас он просто сопоставляет выражение T => T.Foo() с типом Action<T> по приведенным ниже правилам. Чтобы неявное преобразование существовало, должны выполняться следующие условия:
1.1.2.3.1) Количество параметров в списке параметров лямбды и делегата должны совпадать. Условие выполняется (1=1).
1.1.2.3.2) Если список параметров лямбды не содержит явных указаний типов, то делегат не должен иметь ref/out параметров. Условие выполняется.
1.1.2.3.3) Теперь компилятор дает каждому из параметров лямбды тип соответствующего параметра делегата. То есть, единственный параметр T получает тип T. После того, как параметры получили типы, компилятор проверяет тело лямбды на корректность ("If... when each parameter of F is given the type of the corresponding parameter in D, the body of F is a valid expression"). Тело лямбды — это выражение T.Foo(), где T — это параметр типа T. Это выражение обрабатывается следующим образом:
1.1.2.3.3.1) Надо определить, что такое T.Foo. Синтаксически это member-access. Правила его обработки приведены в 7.5.4 Member access. Так как T — это переменная типа T, то следует выполнить member lookup в типе T. Правила member lookup приведены в 7.3 Member lookup.
1.1.2.3.3.1.1) Определим множество доступных членов с именем Foo в типе T. Внимание! Здесь нигде не написано, что ищутся только экземплярные (не статические) члены. Ищем public члены с именем Foo, определенные в типе T или его базовом типе object. Находится один статический метод.
1.1.2.3.3.1.2) Выбрасываем все члены, имеющие модификатор override. Таких нет.
1.1.2.3.3.1.3) Выбрасываем все nested типы, которые имеют типы-параметры в своей декларации. Таких нет.
1.1.2.3.3.1.4) Так как выражение T.Foo является частью выражения T.Foo(), которое синтаксически является invocation-expression, то член Foo считается invoked.
Поэтому выбрасываем все non-invocable члены. Таких нет.
1.1.2.3.3.1.5) Для каждого члена во множестве, помечаем на удаление все члены, которые он скрывает. На данный момент у нас во множестве есть один член — статический метод Foo(), поэтому помечаем все члены, которые он скрывает. Таких нет.
1.1.2.3.3.1.6) Помечаем на удаление скрытые члены интерфейсов. Таких нет, поэтому ничего не делаем.
1.1.2.3.3.1.7) Удаляем члены, помеченые на удаление. Таких нет.
1.1.2.3.3.1.8) Определяем результат member lookup. Так как множество состоит из одного статического метода Foo(), то результатом будет группа методов, состоящая из этого метода.
Возвращаемся к 1.1.2.3.3.1) Таким образом, T.Foo — это группа методов, состоящая из одного метода.
1.1.2.3.3.2) Значит T.Foo() — это method invocation. Алгоритм дальнейших действий описан в 7.5.5.1 Method invocations.
1.1.2.3.3.2.1) Первым делом, надо составить множество методов-кандидатов. Для этого надо перебрать все методы (то есть, проверить единственный метод) в группе методов T.Foo. Рассматриваем статический метод Foo(), определенный в типе T. Это не-generic метод, поэтому для добавления его во множество кандидатов должны выполняться следущие условия:
1.1.2.3.3.2.1.1) При вызове, у группы методов T.Foo должен отсутствовать список типов-аргументов. Условие выполняется.
1.1.2.3.3.2.1.2) Метод Foo() должен быть применим к списку аргументов (). Правила проверки применимости описаны в 7.4.3.1 Applicable function member. Так как метод не имеет модификатора params, то чтобы он был применим, то количество аргументов должно совпадать с количеством параметров. Условие выполняется (0=0). Так как аргументов ноль, то больше никаких требований нет.
Возвращаемся к 1.1.2.3.3.2.1) Так как все условия выполнены, метод Foo() попадает во множество методов-кандидатов. Других методов в рассматриваемой группе методов нет, поэтому множество методов-кандидатов состоит из одного статического метода Foo(), определенного в типе T.
1.1.2.3.3.2.2) Множество методов-кандидатов урезается, чтобы содержать методы только из наиболее производных типов. Так как метод Foo() определен в классе T, то удаляем все методы, определенные в его базовом типе object и в любых интерфейсах. Таких нет.
1.1.2.3.3.2.3) Определяем лучший метод с помощью правил приведенных 7.4.3 Overload resolution. Так как мы имеем единственный метод, то он автоматически становится лучшим.
1.1.2.3.3.2.4) Производим final validation выбранного метода. Внимание! Именно на этом шаге впервые проверяется, является ли метод статическим или экземплярным. Здесь срабатывает правило 7.5.4.1: доступ идет через точку после параметра T, но этот параметр имеет тип T, поэтому допустимы и статические, и экземплярные члены. То есть наш статический метод Foo() проходит валидацию. Кстати, на этом же шаге проверялись бы и констрейнты на типах-параметрах лучшего метода, если бы он был generic.
Возвращаемся к 1.1.2.3.3.2) Значит, T.Foo() — это корректное выражение.
Возвращаемся к 1.1.2.3.3) Значит, во время анализа тела лямбды не было обнаружено ошибок, поэтому тело лямбды корректно. Теперь проверяем совпадение возвращаемого типа лямбды и делегата. возвращаемый тип делегата — void, поэтому просто требуется, чтобы тело лямбды было выражением, синтаксически допустимым в качестве statement-expression (проверяется по 8.6 Expression statements). Тело лямбды — это invocation-expression, поэтому оно проходит проверку.
Возвращаемся к 1.1.2.3) Таким образом, неявное преобразование от выражения T => T.Foo() к типу Action<T> существует.
Возвращаемся к 1.1.2) Таким образом, метод Bar(Action<T>) применим к списку аргументов (T => T.Foo()).
Возвращаемся к 1.1) Так как все условия выполнены, метод Bar(Action<T>) попадает во множество методов-кандидатов.
1.2) Рассматриваем метод Bar(Action<S>). Это не-generic метод, поэтому для добавления его во множество кандидатов должны выполняться следущие условия:
1.2.1) При вызове, у группы методов Bar должен отсутствовать список типов-аргументов. Условие выполняется.
1.2.2) Метод Bar(Action<S>) должен быть применим к списку аргументов (T => T.Foo()). Правила проверки применимости описаны в 7.4.3.1 Applicable function member. Так как метод не имеет модификатора params, то чтобы он был применим, должны выполняться следующие условия:
1.2.2.1) Количество аргументов должно совпадать с количеством параметров. Условие выполняется.
1.2.2.2) Если у параметра нет модификаторов ref/out, то их не должно быть и у аргумента. Условие выполняется.
1.2.2.3) Должно существовать неявное преобразование от аргумента T => T.Foo() к типу параметра Action<S>. В данном случае аргумент является лямбда-выражением, поэтому правила, определяющие наличие или отсутствие неявного преобразования, описаны в 6.5 Anonymous function conversions. Чтобы неявное преобразование существовало, должны выполняться следующие условия:
1.2.2.3.1) Количество параметров в списке параметров лямбды и делегата должны совпадать. Условие выполняется (1=1).
1.2.2.3.2) Так как список параметров лямбды неявно типизирован, то спискок делегат не должен иметь ref/out параметров. Условие выполняется.
1.2.2.3.3) Теперь компилятор дает каждому из параметров лямбды тип соответствующего параметра делегата. Внимание! Тот факт, что эта лямбда раньше проверялась на соответствие с другим делегатом, и параметр T уже получил ранее другой тип, никакого значения не имеет. Теперь единственный параметр T получает тип S, и начинается новый, полностью независимый процесс анализа тела лямбды. Тело лямбды — это выражение T.Foo(), где T — это параметр типа S. Это выражение обрабатывается следующим образом:
1.2.2.3.3.1) Этот шаг дословно совпадает с шагом 1.1.2.3.3.1 с заменой типа T на тип S. Таким образом, T.Foo — это группа методов, состоящая из одного метода.
1.2.2.3.3.2) Значит T.Foo() — это method invocation. Алгоритм дальнейших действий описан в 7.5.5.1 Method invocations.
1.2.2.3.3.2.1-3) Эти шаги дословно совпадают с шагами 1.2.2.3.3.2.1-3 с заменой типа T на тип S. Статический метод Foo(), определенный в типе S, становится лучшим.
1.2.2.3.3.2.4) Производим final validation выбранного метода. В данном случае правило 7.5.4.1 неприменимо: доступ идет через точку после параметра T, но этот параметр имеет тип S. Поэтому лучший метод должен быть экземплярным. Но тот метод, который мы нашли — статический, поэтому он не проходит валидацию.
Возвращаемся к 1.2.2.3.3.2) Значит, T.Foo() — это некорректное выражение.
Возвращаемся к 1.2.2.3.3) Значит, во время анализа тела лямбды были обнаружены ошибки, поэтому тело лямбды некорректно.
Возвращаемся к 1.2.2.3) Таким образом, неявное преобразование от выражения T => T.Foo() к типу Action<S> не существует.
Возвращаемся к 1.2.2) Таким образом, метод Bar(Action<S>) не применим к списку аргументов (T => T.Foo()).
Возвращаемся к 1.2) Метод Bar(Action<S>) не попадает во множество методов-кандидатов.
Возвращаемся к 1) Таким образом, множество методов-кандидатов состоит из одного метода Bar(Action<T>).
2) Множество методов-кандидатов урезается, чтобы содержать методы только из наиболее производных типов. Так как метод Bar (Action<T>) определен в классе Program, то удаляем все методы, определенные в его базовом типе object и в любых интерфейсах. Таких нет.
3) Определяем лучший метод с помощью правил приведенных 7.4.3 Overload resolution. Так как мы имеем единственный метод, то он автоматически становится лучшим.
4) Производим final validation выбранного метода. Так как доступ идет через simple-name из статического члена Main, то найденный статический метод Bar(Action<T>) проходит валидацию.
Теперь надо определить, какой же тип имеет параметр T у аргумента T => T.Foo(). Согласно 7.14 Anonymous function expressions, этот тип определяется типом параметра делегата, к которому приводится данная лямбда. Так как overload resolution выбрал метод Bar(Action<T>), то лямбда приводится к типу Action<T>, и параметр T окончательно получает тип T. Опять-таки, здесь совершенно не важно, что ранее были попытки сопоставления этой лямбды с другими делегатами. Повторив шаги 1.1.2.3.3.1-2, мы узнаем, что T.Foo() в теле лямбды — это корректное выражение, вызов статического метода, определенного в типе T.

Теперь переходим к анализу выражения
Bar(S => S.Foo())

Все шаги, приведенные выше, повторяются, только T и S меняются местами. В результате мы узнаем, что S.Foo() в теле лямбды — это корректное выражение, вызов статического метода, определенного в типе S.

Остались какие-то неясности?
Re[6]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 08.02.09 20:21
Оценка:
Здравствуйте, VladD2, Вы писали:

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

VD>
VD>using System;

VD>module Program
VD>{
VD>    Main() : void
VD>    {
VD>      _ = Foo(a => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => Foo(_ => a))))))));
VD>    }

VD>    Foo(_ : Func[byte, byte]) : string { null }
VD>    Foo(_ : Func[short, short]) : string { null }
VD>    Foo(_ : Func[int, int]) : string { null }
VD>    Foo(_ : Func[long, long]) : string { null }
VD>    Foo(_ : Func[string, string]) : string { null }
VD>}

VD>Если подвести мышку к параметру "a", то молниеносно выведится "(function parametr) a : string".

К сожалению, nemerle.org пока в дауне, и у меня под рукой есть только версия от марта 2008. Она намертво зависает на этом примере. Я рад, если это улучшилось за год. Я предлагал варианты ускорения этих алгоритмов C# compiler team, которые должны были сделать компиляцию этого конкретного примера (и схожих с ним) молниеносной, но они решили, что дешевле иметь простой (хотя и медленный на экзотических примерах) алгоритм, чем хитроумный, в котором могут быть трудноуловимые баги, и который все равно не обеспечит компиляцию всех вариантов кода за линейное к числу перегрузок время.

Иннтересно, что несколько месяцев назад ты утверждал
Автор: VladD2
Дата: 01.10.08
, что этот код не компилируется и не должен компилироваться в немерле.
Re[8]: [Этюд, C#] Overload resolution
От: nikov США http://www.linkedin.com/in/nikov
Дата: 08.02.09 20:37
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Значит недостаточно. Для лямбд, в C#, тип параметров (если он не задан явно) всегда берется из места куда он подставляется. Это может быть параметр функции или переменная. Вывода типов из использования в C# нет.

Но "место, куда он подставляется" может быть параметром перегруженного метода. В этом случае использование параметра лямбды в теле лямбды может определить, какой именно перегуженный метод будет вызван, и какой тип, в конечном счете, получит параметр лямбды. Помнишь пример, который я приводил
Автор: nikov
Дата: 05.02.09
в форуме по немерле? Вот, как с ним справляется C#:
using System;

class Program
{
    static void Main()
    {
        Foo(x => Console.WriteLine(x.Message));       
    }

    static void Foo(Action<Exception> _) {}
    static void Foo(Action<string> _) {}
}

Как видишь, вывод типов из использования в C# все-таки есть. Но он ограничивается анализом использования внутри лямбд.

VD>Попробуй на досуге повторить на C# вот этот немерловый код:

Да, я осведомлен, что есть многие варианты, которые C# типизировать не в состоянии.
Re[7]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:24
Оценка:
Здравствуйте, nikov, Вы писали:

N>Я готов обсудить вопросы неудачных решений в стандарте после того, как мы убедимся, что здесь нет ошибок реализации, т.е. поведение компилятора соответствует текущей версии стандарта. Нельзя сказать, что я в восторге от того, как overload resolution взаимодействует с лямбдами.


Боюсь, что здесь ничего не поделать. Ведь или прийдется отказаться от вывода типов (хотя бы внутри выражений определяющих перегрузку), или от перегрузок. И то, и то плохо. Вот ограничить перегрузку можно.

Кстати, о перегрузках. Как в F# можно взывать метод Where со следующей перегрузкой:
public static IEnumerable<TSource> Where<TSource>(this IEnumerable<TSource> source, Func<TSource, int, bool> predicate)

для списка кортежей:
let xs = [(1, "1"), (2, "2")];


N>Как я понял, твое основное возражение заключается в том, что 7.5.4.1 не может применяться, потому что он требует, чтобы имя параметра совпадало с именем его типа, а в моем примере неизвестно, что параметр T имеет тип T. Это не так. Сейчас я продемонстрирую, почему в момент применения правила 7.5.4.1 известно, что параметр T имеет тип T. Попробуй на время отложить свою интуицию, воспитанную на немерле, и внимательно проследить по спецификации C#, что здесь происходит. Для удобства я пронумеровал шаги рассуждения. Если тебе не понятно, что происходит на некотором шаге, укажи его номер...


N>...


N>Остались какие-то неясности?


Убедил. Ты прав!

Мня смутило имя. Почему-то воспринимал T в Action<T> как дженерик-аргумент. От того и не мог понять откуда берется ассоциация типа Т с параметром Т.

Однако такое поведение выглядит странно. В Немерле пункт 7.5.4.1 не реализован, по этому он подобный код не скомпилирует. Но вот такой вариант:
using System;

module Program
{
  Main() : void
  {
    Bar(T => { T.Foo(); });
    Bar(S => { S.Bar(); });
    
    Bar(t => { t.Foo(); });
    Bar(s => { s.Bar(); });
  }
  
  Bar(_ : Action[T]) : void { Console.WriteLine("T"); }
  Bar(_ : Action[S]) : void { Console.WriteLine("S"); }
}

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

class S
{
  public Bar() : void { }
}

...скомпилирует.

Так что если в Немерле реализовать 7.5.4.1, то и он будет вести себя "так странно".

В принципе 7.5.4.1 иногда удобен. Но подобные эффекты мне совсем не нравятся.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:36
Оценка: +1
Здравствуйте, nikov, Вы писали:
зования в C# нет.
N>Но "место, куда он подставляется" может быть параметром перегруженного метода. В этом случае использование параметра лямбды в теле лямбды может определить, какой именно перегуженный метод будет вызван, и какой тип, в конечном счете, получит параметр лямбды. Помнишь пример, который я приводил
Автор: nikov
Дата: 05.02.09
в форуме по немерле? Вот, как с ним справляется C#:

N>
N>using System;

N>class Program
N>{
N>    static void Main()
N>    {
N>        Foo(x => Console.WriteLine(x.Message));       
N>    }

N>    static void Foo(Action<Exception> _) {}
N>    static void Foo(Action<string> _) {}
N>}
N>

N>Как видишь, вывод типов из использования в C# все-таки есть. Но он ограничивается анализом использования внутри лямбд.

Это не вывод типов из использования. Тип берется из типа аргумента Foo.
Приведенные примеры немерлового кода не по зубам Шарпу именно из-за отсутствия вывода типов из использования.

Другое дело, что вывод тип из использования резко усложнят алгоритм вывода типов, что делает создание быстрого компилятора не простой задачей.

VD>>Попробуй на досуге повторить на C# вот этот немерловый код:

N>Да, я осведомлен, что есть многие варианты, которые C# типизировать не в состоянии.

Дык — это именно потому, что Шарп не может взять тип задним числом. Ему всегда нужно давать тип в самом начале. Явно, не явно без разницы. Главное, что тип обязан быть.

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

Кстати, это нехило мешает макросам. В макрах иногда полезно знать тип, но тип может быть неизвестен очень долгое время. Есть конечно механизмы позволяющие дождаться момента когда тип станет известен, но их использование тоже усложняет реализацию. Потом об этом банально надо помнить. В общем, во многих радостях многие печали.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:49
Оценка:
Здравствуйте, nikov, Вы писали:

N>К сожалению, nemerle.org пока в дауне,


Да, уж.

N>и у меня под рукой есть только версия от марта 2008. Она намертво зависает на этом примере.


Последняя версия точно работает. Могу скриншо вывесить или дать ссылку на зеркало СВН-на на нашем сайте.

N> Я рад, если это улучшилось за год. Я предлагал варианты ускорения этих алгоритмов C# compiler team, которые должны были сделать компиляцию этого конкретного примера (и схожих с ним) молниеносной, но они решили, что дешевле иметь простой (хотя и медленный на экзотических примерах) алгоритм, чем хитроумный, в котором могут быть трудноуловимые баги, и который все равно не обеспечит компиляцию всех вариантов кода за линейное к числу перегрузок время.


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

N>Иннтересно, что несколько месяцев назад ты утверждал
Автор: VladD2
Дата: 01.10.08
, что этот код не компилируется и не должен компилироваться в немерле.


И правда!
Значит, то что он теперь работает — это моих рук дело. Я тут в последнее время доработал алгоритм разрешения перегрузки Немерла. Цель как раз была сделать его по поведению ближе к Шарпу. Это было нужно для того, чтобы можно было пользоваться ЛИНК-ом.

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

В общем, работает. И на данном примере весьма шустро.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: [Этюд, C#] Overload resolution
От: VladD2 Российская Империя www.nemerle.org
Дата: 09.02.09 06:51
Оценка:
Здравствуйте, Undying, Вы писали:

U>Ошибочное поведение внесенное в документацию не перестает быть ошибочным.


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