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

Основная причина этого заключается в том, что, как и в C#, не все классы должны быть предварительно объявлены перед использованием, поэтому, когда идентификатор анализируется после <, это может быть либо выражение типа valueA < valueB или это может быть аргумент типа valueA<valueB>.

Тогда я подумал, может быть, если есть закрывающий >, то его можно проанализировать как аргумент типа, но потом я вспомнил, что хотел, чтобы мой язык имел перегрузку операторов, поэтому выражения вроде valueA < valueB > (valueC) могли быть совершенно действительный.

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

Выражение foo < bar > (2) в приведенном ниже коде должно быть абсолютно правильным выражением из-за класса с перегруженными операторами < и >.

Насколько мне известно, выражение должно быть проанализировано как (foo (2), но вместо этого я получаю сообщение об ошибке, указывающее, что "Переменная 'foo' не может использоваться с аргументами типа ." Чтобы доказать, что это выражение должно быть допустимым, я поменял местами знаки < и >, чтобы выражение выглядело как < code>foo > bar < (2), и эта программа скомпилировала и распечатала именно то, что вы ожидаете, MainClass+baz.

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

using System;

class MainClass {
  public static void Main (string[] args) {

    baz foo = new baz();

    baz bar = new baz();

    // perfectly valid expression, results in error: The variable `foo' cannot be used with type arguments
    Console.WriteLine(foo < bar > (2));

  }

  class baz {
    public static baz operator<(baz l, baz r) {
      return l;
    }

    public static baz operator>(baz l, baz r) {
      return l;
    }

    public static baz operator<(baz l, int r) {
      return l;
    }

    public static baz operator>(baz l, int r) {
      return l;
    }

  }

  public String toString() {
    return "baz";
  }

}

Мой вопрос в том, как решить эту проблему в компиляторе С# и других языках?

Я вижу несколько вариантов:

  1. Сообщить об ошибке в неоднозначной ситуации
  2. Может быть, есть еще какой-то контекст, который можно использовать для разрешения этой двусмысленности.
  3. C# существенно сломан, и нам следует разработать новый синтаксис для аргументов типа в будущих языках. Если да, то как это должно выглядеть?

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

0
Mashpoe 1 Ноя 2019 в 02:14

1 ответ

foo - локальная переменная, но вы пытаетесь использовать ее как имя типа класса...

Следовательно, это вовсе не совершенно правильное выражение.

Вы не можете написать:

baz foo = new baz();
baz bar = new baz();

Console.WriteLine(foo<bar>(2));

Но вы можете написать:

var instance = baz<int>(2);

Если у вас есть:

class baz<T>
{
}

Здесь вы пытаетесь создать экземпляр класса baz с параметром универсального типа int без использования ключевого слова new, или, возможно, это может быть неправильно написанное приведение, но компилятор не может этого знать.

Когда вы определяете класс baz<T>, это то, что называется открытым универсальным типом.

И когда вы создаете экземпляр baz<int>, это так называемый закрытый сконструированный универсальный тип.

Вы пишете приведенный ниже код должен быть абсолютно корректным выражением из-за класса с перегруженными операторами < и >...

Но компилятор не может интерпретировать val1 < val2 > (val3) как val1 < val2 && val2 > (val3), как с математическим правилом, или как (val1 < val2) > (val3), как вы ожидаете, потому что оператор <>() имеет приоритет над < и >.

Компилятор думает, что вы пытаетесь использовать baz of T, поэтому компилятор выводит ошибку, потому что компилятор не может выбрать между двумя случаями, между <>() и < next >.

Таким образом, компилятор интерпретирует это как Type<Type>(parameter) и ничего больше.

0
1 Ноя 2019 в 07:39
Спасибо за ответ, но я не думаю, что вы полностью поняли мой код или мой вопрос. Я перегрузил оба операторы < и >, чтобы они возвращали базовые объекты, а не логические значения, поэтому ваша аналогия 2 > 1 < (3) оценивается как true > 3 не применима здесь. Выражение в приведенном выше коде foo > bar < (2) будет оценено как (baz obj) < (2), которое затем будет оценено как (baz obj). Я даже сказал, что оно компилируется, потому что оно компилируется, и, поскольку обращение foo < bar > (2) будет иметь тот же порядок старшинства, оно также должно быть допустимым выражением. Какой из трех вариантов выбрать?
 – 
Mashpoe
1 Ноя 2019 в 06:41
1
Вы должны написать явно: (foo<bar)>(2) или foo < bar > 2, потому что оператор <>() имеет приоритет.
 – 
user12031933
1 Ноя 2019 в 06:46
Я знаю об этом, но разве это не совершенно правильное выражение? Ответ положительный, если только стандарт C# не говорит что-то конкретное об этой проблеме. Я знаю разницу между аргументами типа и выражениями >, и если вы прочтете мой вопрос, то увидите, что я хочу знать, должен ли C# сообщать об этом как об ошибке, и как я могу избежать этой проблемы в мой собственный язык, который я разрабатываю. Я четко и подробно описал, что знаю, в чем проблема.
 – 
Mashpoe
1 Ноя 2019 в 06:52
Кроме того, <>() не имеет приоритета, просто <>, ваш второй пример не скомпилируется, а первый будет. Это было моей ошибкой за то, что я включил ненужный () вокруг 2 в моем вопросе.
 – 
Mashpoe
1 Ноя 2019 в 06:57
1
Я не знаю. Я не читал. p7-8 и p88-91 и p158 и после ? Возможно, вы можете попросить @EricLippert, например, или напрямую отправить дело команде Mono.
 – 
user12031933
1 Ноя 2019 в 08:03