2009-11-28 3 views
15

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

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

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

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

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

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

Если бы нам понадобилась абстракция для моноида, мы могли бы неплохо написать интерфейс и позволить нашим типам реализовать это, но встроенные типы, такие как 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++ или утиную печать. Код типа явно создан по типам, а не только удовлетворен по соглашению. Более того, он может нести полезные реализации, а не просто определять интерфейс.

+3

.Net поддерживает один тип класс: новый. Я предполагаю, что они не реализованы, потому что то, что .Net уже «достаточно хорошо», по сравнению со стоимостью реализации. –

+0

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

+0

@Steve Jessop: Мой комментарий не относится к вашему сообщению в частности (это действительно очень полезный ответ) - я просто хотел предотвратить возможность непонимания вопроса в целом. – Dario

ответ

9

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

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

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

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

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

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

+0

«В ответ на ваш вопрос - любое официальное заявление« Я реализую этот интерфейс »имеет один большой недостаток, что он требует, чтобы интерфейс был изобретен до реализации». Я не думаю, что это относится к классам. Сторонний код может сказать, что «тип A там реализует тип B над * там *, вот как ...» –

+0

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

4

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

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

Что касается того, почему разработчик языка (например, Guido, в случае Python) выбирает таких «волков в овечьей одежде» как ABC Python, по сравнению с более простыми аналогичными типами Haskell, которые я предложил с 2002 года , это более сложный вопрос. В конце концов, это не так, как если бы у Python была какая-либо компромиссная идея против концепций заимствований из Haskell (например, выражение для составления списка/генераторных выражений - Python здесь нуждается в двойственности, а Haskell этого не делает, потому что Haskell «ленив»). Лучшая гипотеза, которую я могу предложить, заключается в том, что к настоящему времени наследование настолько знакомо большинству программистов, что большинство разработчиков языка считают, что они могут получить более легкое признание, бросая что-то в этом направлении (хотя дизайнеры Go должны заслуживать похвалы за то, что не сделали этого).

+4

Python получил ABC хотя бы отчасти потому, что Haskell болит мозгом Гвидо. По крайней мере, вот как я это помню. –

-3

Попробуйте определить Матроид, что мы и делаем (логарифмически и не устно говорим о матроиде), и это все еще вероятно что-то вроде структуры C. Liskov principle (последний призер для туринга) становится слишком абстрактным, слишком категоричным, слишком теоретическим, менее обрабатывает фактические данные и более чистую теоретическую классную систему, для решения прагматических задач handson кратко просматривает это, что выглядело как PROLOG, код о коде о коде о коде. .. в то время как алгоритм описывает последовательности и путешествия, которые мы понимаем на бумаге или доске. Зависит от той цели, которую вы имеете, решая проблему с минимальным кодом или самым абстрактным.

+0

Извините - что вы пытаетесь сказать? Посмотрите на Haskell - проблема, решаемая с минимальным кодом **, обычно является самой абстрактной. – Dario

+0

Wahuh ??????????????????? –

+0

цитируя абстрактную бессмыслицу: «Пусть q (x) - свойство, доказуемое об объектах x типа T. Тогда q (y) должно быть истинным для объектов y типа S, где S - подтип T." связанный с вопросом, обвиняемым, это моя вещь. –

0

Какова аргументация против товарных знаков?

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

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

не так. Посмотрите на структурно-типизированных системе объекта OCaml, например:

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

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

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

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

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

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

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

0

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

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

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

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

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

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

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

Clojure: Как было сказано выше, Clojure имеет что-то вроде классов типа в виде протоколов.

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

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

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

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

(Конечно, механизм расширения не так широк, как синтаксис instance … where Haskell. Методы расширения не «действительно» прикреплены к типу, они реализованы как синтаксическое преобразование. В конце концов, это не так сделать очень большое практическое значение.)

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

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

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

F #: Я нашел сообщение на форуме, объясняющее why type classes might never get introduced into F#. Это объяснение связано с тем, что F # имеет именную, а не структурную систему типов. (Хотя я не уверен, является ли это достаточной причиной для того, чтобы у F # не было классов типов.)

1

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

Что вы хотите, это невиртуальный ad hoc полиморфизм.

  • Специальные: реализация может варьироваться
  • невиртуальный: по соображениям производительности; compiletime dispatch

Остальное - это сахар, на мой взгляд.

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 

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

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

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

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». Также можно было бы поместить реализацию в класс вместе с другими методами и добавить «adhoc interface» в список поддерживаемых интерфейсов.

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

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

+0

Возможно, вы захотите по-разному вдуть int Foo (float x) в разных контекстах. SuperSqaure, однако, тогда нужно будет знать, какой из них взять. Было бы хорошо иметь правильное обсуждение того, почему эта дополнительная функция понадобится и почему классные классы в haskell выглядят так, как они выглядят. –

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