Вопрос

Типовые классы Кажется, это прекрасная возможность писать общие и повторно используемые функции очень последовательным, эффективным и расширяемым способом.Но все равно нет «мейнстрим-язык» обеспечивает их. Напротив: Концепции, которые представляют собой вполне аналогичную идею, были Исключенный из следующего C++!

Каковы аргументы против классов типов?Очевидно, многие языки ищут способ решения подобных проблем:.NET ввел общие ограничения и интерфейсы, такие как IComparable которые позволяют такие функции, как

T Max<T>(T a, T b) where T : IComparable<T> { // }

работать со всеми типами, реализующими интерфейс.

Вместо этого Scala использует комбинацию черты и так называемый неявные параметры/просмотреть границы, которые автоматически передаются универсальным функциям.

Но обе концепции, показанные здесь, имеют большие недостатки: интерфейсы основаны на наследовании и, следовательно, относительно медленны из-за косвенности, и, кроме того, нет возможности позволить существующему типу реализовать их.

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

Вместо этого неявные параметры несовместимы с обычными интерфейсами/особенностями.

С классами типов проблем не будет (псевдокод)

typeclass Monoid of A where
    static operator (+) (x : A, y : A) : A
    static val Zero : A 
end

instance Int of Monoid where
   static operator (+) (x : Int, y : Int) : Int = x + y
   static val Zero : Int = 0
end

Так почему бы нам не использовать классы типов?Есть ли у них все-таки серьезные недостатки?

Редактировать:Пожалуйста, не путайте классы типов со структурной типизацией, шаблонами чистого C++ или утиной типизацией.Класс типов — это явно созданный экземпляр по типам и не просто довольны условно.Более того, он может содержать полезные реализации, а не просто определять интерфейс.

Это было полезно?

Решение

Концепции были исключены, потому что комитет не думал, что они могли бы сделать их вовремя, и потому что они не считались необходимыми для выпуска. Не то, чтобы они не думали, что они хорошая идея, они просто не думают, что выражение их для C ++ является зрелым: http://herbsutter.wordpress.com/2009/07/21/trip-report/

Статические типы пытаются предотвратить передачу объекта в функцию, которая не удовлетворяет требованиям функции. В C ++ это огромная проблема, потому что в то время, когда к объекту обращается код, нет никакой проверки, что это правильно.

Концепции пытаются предотвратить передачу параметра шаблона, который не соответствует требованиям шаблона. Но в то время как компилятор обращается к параметру шаблона, уже проверяет, что это правильно, даже без Concepts. Если вы попытаетесь использовать его так, как он не поддерживает, вы получите ошибку компилятора [*]. В случае интенсивного использования шаблона вы можете получить три экрана, заполненных угловыми скобками, но в принципе это информативное сообщение. Необходимость отлавливать ошибки до неудачной компиляции менее актуальна, чем необходимость отлавливать ошибки до неопределенного поведения во время выполнения.

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

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

[*] Обычно. Есть некоторые исключения, например, система типов C ++ в настоящее время не запрещает вам использовать входной итератор, как если бы он был прямым итератором. Для этого вам нужны черты итератора. Один только ввод утки не мешает вам пройти мимо объекта, который ходит, плавает и крякает, но при тщательном осмотре фактически не делает ничего из того, что делает утка, и удивляется, узнав, что вы думали, что это будет ;-)

Другие советы

Интерфейсы не должны быть основаны на наследовании ... это другое и отдельное дизайнерское решение. Новый язык Go имеет интерфейсы, но не имеет наследования, например: & Quot; тип автоматически удовлетворяет любой интерфейс, который определяет подмножество своих методов " ;, как Go FAQ кладет это. рассуждения о наследовании и интерфейсах, вызванные недавним выпуском Go, Simionato может стоить читать.

Я согласен с тем, что классы типов являются еще более мощными, в основном потому, что, например, abstract базовые классы , они позволяют вам дополнительно указать полезный код (определяя дополнительный метод X в терминах других для всех типов, которые в противном случае соответствуют базовому классу, но сами не определяют X) - без наследственного багажа, который ABC (иначе) из интерфейсов) почти неизбежно повезет. Почти неизбежно, потому что, например, азбука Python " верить " что они включают наследование с точки зрения концептуализации, которую они предлагают ... но, на самом деле, они не должны основываться на наследовании (многие просто проверяют наличие и сигнатуру определенных методов, точно так же, как интерфейсы Go).

Что касается того, почему разработчик языка (например, Гвидо в случае с Python) выбрал такой " волки в овечьей шкуре " как азбука Python, по сравнению с более простыми классами типов, подобными Haskell, которые я предлагал еще в 2002 году, ответить на этот вопрос сложнее. В конце концов, это не так, как если бы Python имел какую-либо услугу против заимствования понятий из Haskell (например, списочные выражения / выражения генератора - Python здесь нужна двойственность, в то время как Haskell нет, потому что Haskell & Quot; lazy < !> Quot;). Наилучшая гипотеза, которую я могу предложить, заключается в том, что к настоящему времени наследование настолько знакомо большинству программистов, что большинство разработчиков языков считают, что они могут получить более легкое признание, создавая таким образом вещи (хотя дизайнеры Go должны быть оценены за то, что они этого не сделали).

Начну смело:Я прекрасно понимаю мотивацию его наличия и не могу понять мотивацию некоторых людей выступать против него...

То, что вы хотите, это невиртуальный специальный полиморфизм.

  • для этого случая:реализация может варьироваться
  • невиртуальный:по соображениям производительности;отправка во время компиляции

Остальное, по-моему, сахар.

В C++ уже есть специальный полиморфизм через шаблоны.Однако «концепции» прояснят, какой тип специальной полиморфной функциональности используется тем или иным определяемым пользователем объектом.

В C# просто нет способа сделать это. Подход, который не было бы невиртуальным:Если бы такие типы, как float, просто реализовывали бы что-то вроде «INumeric» или «IAddable» (...), мы, по крайней мере, могли бы написать общий min, max, lerp и на основе этого зажима, Maprange, Bezier (...) .Однако это не будет быстро.Вы этого не хотите.

Способы исправить это:Поскольку .NET в любом случае выполняет JIT-компиляцию, также генерирует другой код для List<int> чем для List<MyClass> (из-за различий в типах значений и ссылочных типах), вероятно, не будет слишком много накладных расходов на создание другого кода для специальных полиморфных частей.Языку C# просто нужен способ выразить это. В одну сторону это то, что ты набросал.

Другой способ — добавить ограничения типа в функцию. с использованием специальная полиморфная функция:

    U SuperSquare<T, U>(T a) applying{ 
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    }
    {
        return Foo(a * a);
    }

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

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

    // adhoc is like an interface: it is about collecting signatures
    // but it is not a type: it dissolves during compilation 
    adhoc AMyNeeds<T, U>
    {
         nonvirtual operator (*) T (T, T) 
         nonvirtual Foo U (T)
    } 

    U SuperSquare<T, U>(T a) applying AMyNeeds<T, U>        
    {
        return Foo(a * a);
    }

В каком-то «основном» месте известны все аргументы типа, и все становится конкретным и может быть скомпилировано вместе.

Чего еще не хватает, так это отсутствия создания различных реализаций.В верхнем примере мы просто использовал полиморфные функции и дайте всем знать...

Реализация опять-таки может идти по пути методов расширения – в их способности добавлять функциональность к любому классу в любой момент:

 public static class SomeAdhocImplementations
 {
    public nonvirtual int Foo(float x)
    {
        return round(x);
    }
 }

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

    int a = SuperSquare(3.0f); // 3.0 * 3.0 = 9.0 rounded should return 9

Компилятор проверяет все «невиртуальные» специальные функции, находит как встроенный оператор с плавающей запятой (*), так и int Foo (float) и, следовательно, может скомпилировать эту строку.

Специальный полиморфизм, конечно, имеет недостаток: вам придется перекомпилировать для каждого типа времени компиляции, чтобы были вставлены правильные реализации.И, вероятно, IL не поддерживает помещение в dll.Но, возможно, они все равно над этим работают...

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

    MyColor a = SuperSquare(3.0f); 
    // error: There are no ad hoc implementations of AMyNeeds<float, MyColor> 
    // in particular there is no implementation for MyColor Foo(float)

Но, конечно, также возможно создание экземпляра класса типа / «интерфейса специального полиморфизма».В сообщении об ошибке будет указано:"The AMyNeeds constraint of SuperSquare has not been matched. AMyNeeds is available as StandardNeeds : AMyNeeds<float, int> as defined in MyStandardLib".Также можно было бы поместить реализацию в класс вместе с другими методами и добавить «специальный интерфейс» в список поддерживаемых интерфейсов.

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

информация:я на твоей стороне.Подобные вещи отстойны в основных статически типизированных языках.Хаскелл показал путь.

  

В чем причина против классов типов?

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

  

Интерфейсы основаны на наследовании и, следовательно, относительно медленны из-за косвенности, и, кроме того, нет возможности позволить существующему типу реализовать их

Не правда. Посмотрите на структурно типизированную объектную систему OCaml, например:

# let foo obj = obj#bar;;
val foo : < bar : 'a; .. > -> 'a = <fun>

Эта foo функция принимает любой объект любого типа, который предоставляет необходимый метод bar.

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

  

Есть ли у них серьезные недостатки в конце концов?

Посмотрите на собственный пример, общая арифметика. F # уже может обрабатывать этот конкретный случай благодаря интерфейсу INumeric. Тип F # Matrix даже использует этот подход.

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

Вопрос, безусловно, должен звучать так: может ли кто-нибудь убедительно доказать за принятие классов типов?

  

Но до сих пор нет "основного языка" предоставляет [классы типов.]

Когда был задан этот вопрос, это могло быть правдой. Сегодня интерес к таким языкам, как Haskell и Clojure, значительно возрос. На Haskell есть классы типов ( class / instance ), Clojure 1.2+ имеет протоколы ( defprotocol / extension ).

  

В чем причина [классов типов]?

Я не думаю, что классы типов объективно "хуже" чем другие механизмы полиморфизма; они просто следуют другому подходу. Таким образом, реальный вопрос заключается в том, хорошо ли они вписываются в определенный язык программирования?

Давайте кратко рассмотрим, чем классы типов отличаются от интерфейсов в таких языках, как Java или C #. В этих языках класс поддерживает только те интерфейсы, которые явно упомянуты и реализованы в определении этого класса. Классы типов, тем не менее, являются интерфейсами, которые могут быть позже добавлены к любому уже определенному типу, даже в другом модуле. Этот тип расширяемости типов, очевидно, сильно отличается от механизмов в определенном «основном направлении». ОО языки.

<Ч>

Давайте теперь рассмотрим классы типов для нескольких основных языков программирования.

Haskell . Нет необходимости говорить, что в этом языке есть классы типов .

Clojure . Как уже говорилось выше, в Clojure есть что-то вроде классов типов в форме протоколов .

C ++ . Как вы уже сказали, концепции были исключены из спецификации C ++ 11.

  

Напротив: концепции, которые являются весьма аналогичной идеей, были исключены из следующего C ++!

Я не следил за всей полемикой вокруг этого решения. Из того, что я прочитал, концепции еще не были «готовы»: еще продолжались дебаты о концептуальных картах. Однако концепции не были полностью оставлены, ожидается, что они войдут в следующую версию C ++.

C # . С языковой версией 3 C # по сути стал гибридом парадигм объектно-ориентированного и функционального программирования. В язык было сделано одно дополнение, концептуально очень похожее на классы типов: методы расширения . Основное отличие состоит в том, что вы (похоже) присоединяете новые методы к существующему типу, а не к интерфейсам.

(Конечно, механизм метода расширения не так элегантен, как синтаксис instance & # 8230; где в Haskell. Методы расширений не "действительно" связаны с типом, они реализованы как синтаксическое преобразование. В конце концов, это не имеет большого практического значения.)

Не думаю, что это произойдет в ближайшее время & # 8212; разработчики языка, вероятно, даже не добавят расширение properties к языку, а расширение interfaces даже пойдет на шаг дальше.

( VB.NET : Microsoft в течение некоторого времени «совместно развивалась» с языками C # и VB.NET, поэтому мои утверждения о C # оказываются верными и для VB.NET .)

Java . Я не очень хорошо знаю Java, но из языков C ++, C # и Java это, вероятно, "самый чистый" вопрос. ОО язык. Я не понимаю, как классы типов вписались бы в этот язык.

F # . Я нашел сообщение на форуме, объясняющее почему классы типов никогда не будут введены в F # . Это объяснение основано на том факте, что F # имеет номинативную, а не структурную систему типов. (Хотя я не уверен, является ли это достаточной причиной того, что F # не имеет классов типов.)

Попробуйте определить Matroid, что мы и делаем (логически, а не устно, говоря Matroid), и это все еще, вероятно, что-то вроде структуры Си. принцип Лискова (последний призер Тьюринга) становится слишком абстрактным, слишком категоричным, слишком теоретическим, менее актуальным Данные и более чистая теоретическая система классов для практического решения проблем кратко взглянули на нее, которая выглядела как PROLOG, код о коде о коде о коде ... в то время как алгоритм описывает последовательности и путешествия, которые мы понимаем, на бумаге или доске. Зависит от того, какая у вас цель, решение проблемы с минимальным кодом или наиболее абстрактным.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top