Злоупотребляет ли это системой общих типов? - PullRequest
3 голосов
/ 07 октября 2011

Предположим, у нас был компилятор, скомпилированный в .Net, где все методы были бы универсальными для всех их типов (параметров и локальных), и вы не указывали бы типы при написании метода, а вместо этого ограничения.

При компиляции метод может выглядеть следующим образом:

interface IFoo<T> {
    T Foo();
}
interface IBar<TParam, TResult> {
    TResult Bar(TParam param);
}
public TResult FooBar<TItem, TParam, TIntermediate, TResult>(A item, B param)
    where TItem: IFoo<TIntermediate>
    where TIntermediate: IBar<TParam, TResult> {
        return item.Foo().Bar(param);
}

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

FooBar = (item [I], param [P]) [I: IFoo<X>, X: IBar<P, R>] => [R] {
    item.Foo().Bar(param)
}

Будет ли программа, в которой большинство методов скомпилировано подобным образом (кроме тех, в которых точные типы могут быть выведены из использования), будет с использованием усовершенствованной системы универсальных типов .Net или злоупотребляя it?

Например, может ли такая вещь замедлить CLR, потому что в ней слишком много сопряженных типов, или она просто «сработает» так же эффективно, как обычный код?

Ответы [ 4 ]

2 голосов
/ 08 октября 2011

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

Длинный ответ:

Для предлагаемогоновый язык, вы можете иметь синтаксис всегда таким, но в идеале компилятор должен пытаться сделать фактические скомпилированные методы как можно более универсальными.Это улучшает возможности разработчика C # / VB / etc, импортирующего ваши библиотеки, так как я не верю, что общие ограничения хорошо проявляются в Intellisense.Это также уменьшает количество общих экземпляров, которые должна выполнить среда выполнения.Однако сделать этот алгоритм может оказаться довольно сложно.Например, я изначально думал, что ваш пример метода может быть переписан так:

TResult FooBar<TParam, TResult>(IFoo<IBar<TParam, TResult>> item, TParam param)
{
    return item.Foo().Bar(param);
}

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

С другой стороны, пример плохого времени для генерирования универсального метода:

Write = (stream [S], data [D]) [S: Stream, D: byte()] => [] {
    stream.Write(data, 0, data.Length)
}

Здесь прямой перевод может дать:

public void Write<S, D>(S stream, D data) where S : Stream, D : byte[]
{ stream.Write(data, 0, data.Length); }

где ни один из общих параметров не требуется из-за стандартного полиморфизма, и вы можете вместо этого вывести это:

public void Write(Stream stream, byte[] data)
{ stream.Write(data, 0, data.Length); }
2 голосов
/ 07 октября 2011

Существует серьезная проблема с процедурами, которые объединяют ограничения универсального типа: нет общего способа типизировать объект, чтобы он мог быть передан такой подпрограмме - ситуация очень отличается от подпрограмм без таких параметров типа.

Если рутина ожидает, например, IFoo, и я хочу передать ему некоторый объект, который я считаю IFoo, я могу привести объект к IFoo и передать его в рутину. Если возможно, что объект может быть IFoo, компилятор сгенерирует код, который будет предполагать, что объект является IFoo, и потерпит неудачу, если это не так.

Если подпрограмма принимает параметр универсального типа, переданный объект должен иметь тип времени компиляции , который удовлетворяет всем ограничениям. Если есть только одно ограничение и это конкретный базовый тип или интерфейс (например, IFoo), это легко - приведите его к типу, подразумеваемому ограничением. Этот тип объекта компиляции после преобразования типа будет удовлетворять ограничению, и если тип времени выполнения объекта будет удовлетворять ограничению, приведение будет успешным.

Однако возникают трудности, если кто-то хочет вызвать процедуру Wowzo, параметр которой ограничен для реализации нескольких ограничений, например, интерфейс IFoo и базовый класс Wuzzle, но объекты, которые нужно передать этой подпрограмме, не имеют общего базового типа, удовлетворяющего ограничениям. Например, предположим, что классы Wuzzle1, Wuzzle2 и Wuzzle3 все реализуют IFoo и все наследуют от Wuzzle, но Wuzzle не реализует IFoo. У меня есть массив Wuzzle, в нем есть элементы Wuzzle1, Wuzzle2 и Wuzzle3. Если элементом массива является Wuzzle1, я могу привести его к Wuzzle1 и передать его Wowzo. Если это будет Wuzzle2, я могу привести его к Wuzzle2 и передать Wowzo. Точно так же с Wuzzle3. К сожалению, нет хорошего «общего» способа передачи элементов массива в Wowzo, без необходимости явно иметь дело со всеми возможными типами, которые могут быть в списке.

1 голос
/ 19 октября 2011

Ну, одной большой проблемой была бы перегрузка метода. CLR не считает эти методы различными.

public void Method<T>(T arg){} where T:IFoo
public void Method<T>(T arg){} where T:IBaz

Конечно, вы можете решить это на своем языке с помощью модификаторов параметров modreq / modopt. Однако это означает, что либо информация теряется для вызывающих абонентов из большинства других языков .NET, либо просто не может быть вызвана другими языками.

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

Наконец, действительно, нет хорошего способа решить проблему комбинаторики ограничений: вам нужно выдать тип, чтобы он удовлетворял системе типов до времени выполнения, чтобы инструкции вроде isinst и castclass работали. Поэтому, если вы хотите поддерживать x as IFoo,IBar, вам нужно создать интерфейс <GeneratedInterface>_IFoo_IBaz:IFoo,IBaz. Тогда фактический тип x должен был бы добавить поддержку сгенерированного интерфейса, поскольку .NET на самом деле не имеет утки.

Вы также можете столкнуться с реификационными болями x as IFoo<T>,IBaz<T>, но T здесь является невозможной комбинацией (например, T в первом случае - это структура, а T во втором - это класс (x на самом деле Baz<T,U>). Это может быть очень аккуратно, но очень трудно создать такой язык.

0 голосов
/ 08 октября 2011

Если итоговый IL из такого языка программирования поддается проверке, то он не злоупотребляет системой типов, потому что она предназначена для запуска всего, что поддается проверке (и кое-чего из того, что не поддается проверке).

Мой вопрос был бы - зачем вам это делать? Что плохого в статических типах аргументов метода, с которыми имеет дело полиморфизм?

...