2016-10-25 1 views
15

Мой вопрос мотивирован Eric Lippert's this blog post. Рассмотрим следующий код:Неоднозначность в выводе типа параметра для выражений лямбда C#

using System; 
class Program { 
    class A {} 
    class B {} 
    static void M(A x, B y) { Console.WriteLine("M(A, B)"); } 
    static void Call(Action<A> f) { f(new A()); } 
    static void Call(Action<B> f) { f(new B()); } 
    static void Main() { Call(x => Call(y => M(x, y))); } 
} 

компилируется успешно и печатает M(A, B), потому что компилятор выясняет, что типы x и y в лямбда-выражений должны быть A и B соответственно. Теперь добавьте перегрузку для Program.M:

using System; 
class Program { 
    class A {} 
    class B {} 
    static void M(A x, B y) { Console.WriteLine("M(A, B)"); } 
    static void M(B x, A y) { Console.WriteLine("M(B, A)"); } // added line 
    static void Call(Action<A> f) { f(new A()); } 
    static void Call(Action<B> f) { f(new B()); } 
    static void Main() { Call(x => Call(y => M(x, y))); } 
} 

Это приводит к ошибке во время компиляции:

error CS0121: The call is ambiguous between the following methods or properties: 'Program.Call(Action<Program.A>)' and 'Program.Call(Action<Program.B>)'

компилятор не может вывести типы x и y. Может быть, что x имеет тип A и y имеет тип B или наоборот, и ни одна из них не может быть предпочтительной из-за полной симметрии. Все идет нормально. Теперь добавьте еще один перегрузки для Program.M:

using System; 
class Program { 
    class A {} 
    class B {} 
    static void M(A x, B y) { Console.WriteLine("M(A, B)"); } 
    static void M(B x, A y) { Console.WriteLine("M(B, A)"); } 
    static void M(B x, B y) { Console.WriteLine("M(B, B)"); } // added line 
    static void Call(Action<A> f) { f(new A()); } 
    static void Call(Action<B> f) { f(new B()); } 
    static void Main() { Call(x => Call(y => M(x, y))); } 
} 

компилируется успешно и печатает M(A, B) снова! Я могу угадать причину. Компилятор разрешает перегрузку Program.Call, пытаясь скомпилировать лямбда-выражение x => Call(y => M(x, y)) для x типа A и для x типа B. Первый успешно, в то время как последний терпит неудачу из-за неоднозначности, обнаруженной при попытке вывести тип y. Поэтому компилятор заключает, что x должен иметь тип A.

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

If it has more than one solution then compilation fails with an ambiguity error.

Есть ли хорошая причина для текущего поведения? Это просто вопрос облегчения жизни компилятора? Или это скорее недостаток компилятора/спецификации?

+0

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

+2

@viveknuna Все методы возвращают 'void', каков тип возвращаемого результата? – Kyle

ответ

12

Интересные сценарии. Давайте рассмотрим, как компилятор анализирует каждый.

В вашем первом сценарии единственная возможность состоит в том, что x есть A и y равно B. Все остальное создает ошибку.

В вашем втором сценарии мы можем иметь x, A, y есть B, или x есть B, y равно A. Либо решение работает, мы не имеем оснований, чтобы предпочесть его, поэтому программа неоднозначна.

Теперь рассмотрим ваш третий сценарий. Начнем с предположения, что x равно B. Если x есть B, тогда y может быть A или B. У нас нет причин предпочитать A или B для y. Поэтому программа, в которой x есть B, неоднозначна. Следовательно, x не может быть B; наше предположение, должно быть, было неправильным.

Таким образом, либо x есть A, либо программа является ошибочной. Может х быть А? Если это так, то y должно быть B. Мы не выводим ошибку, если x является A, и мы выводим ошибку, если x является B, поэтому x должно быть A.

Из этого можно сделать вывод, что x есть A и y является B.

This is weird.

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

Я подозреваю, что ваши трудности заключается в том, что, как представляется, лучше анализ для третьего сценария:

  • х А, у является не удается
  • х А, у есть B работает
  • x is B, y is A works
  • x is B, y is B works
  • поэтому есть три решения, ни один не лучше, поэтому это неоднозначно.

Это не так, как это работает. Скорее, мы даем все возможные назначения типаoutermost лямбда и попытаемся добиться успеха или неудачи для каждый.

Если вы сделаете аргумент, что это немного странно, что «порядок имеет значение» - лямбда снаружи в некотором смысле «привилегирована» над лямбдами внутри, ну, конечно, я могу видеть этот аргумент. Это характер алгоритмов обратного отслеживания.

If it has more than one solution then compilation fails with an ambiguity error.

Это все еще так. В первом и третьем сценариях есть одно решение, которое можно вывести без противоречия; во втором - два решения, и это неоднозначно.

Is there any good reason for the current behavior?

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

Is it just the matter of making the compiler's life easier?

HA HA HA HA HA HA HA HA.

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

Or is it rather a flaw in the compiler/specification?

No.

Целью процесса спецификации было придумать дизайн, который произвел разумные выводы, учитывая виды перегрузок, которые мы видели в стандартных библиотеках LINQ. Я думаю, мы достигли этой цели. «Добавление перегрузки никогда не приводит к тому, что неоднозначная программа становится недвусмысленной», не была в какой-то момент целью процесса спецификации.

+0

Я понимаю правила. Но я думаю, что цель разрешения перегрузки заключается в том, что компилятор пытается лучше понять намерения программиста, обнаруживать и сообщать о двусмысленности, а не делать произвольные выборы. Поэтому при анализе внутреннего лямбда-выражения, предполагающего, что x является B, компилятор может рассматривать неоднозначность иначе, чем другие ошибки. Я имею в виду, вместо того, чтобы думать, что «при условии, что x есть B, выдает ошибку», компилятор мог подумать: «Принимая x, B дает двусмысленность, я ищу двусмысленность, и я только что нашел». Но, возможно, это слишком много. – Bartosz

+2

Тем не менее, я думаю, что предложение «Если у него более одного решения, то компиляция не выполняется с ошибкой двусмысленности». из вашего сообщения в блоге, которое относится к формуле SAT, не соответствует действительности или, по крайней мере, вводит в заблуждение. Если вы измените тело основной функции на «M (a => M (b => M (c => MustBeT (И (Или (a, b, c), Not (c))))));' , код успешно компилируется, хотя формула '(a | b | c) & (! c)' имеет более одного решения. – Bartosz

+1

@ Бартош: Это отличная критика; вы правы, что статья вводит в заблуждение таким образом. –

Смежные вопросы