Здравствуйте, samius, Вы писали:
V>>А только это и важно.
S>Кому как
Если речь о пользователях языка, а не о разработчиках компилятора с этого языка, то всем им (нам) должно быть одинаково. ))
V>>С другой стороны есть механики его обеспечения компилятором — матчинг типа и соответствующей ему ф-ии/метода на этапе компиляции/оптимизации или на этапе исполнения через таблицу ф-ий, как для случая виртуальных ф-ий в ООП или в некоторых сценариях с использованием классов типов в том же Хаскеле.
S>верно. Но цитата из Пирса однозначно отсылает к механике выполнения в рантайме.
Не ведись на это. ))
Хороший теоретик не обязан быть хорошим инженером, и обратное тоже верно.
Не имеет никакой разницы, как в конечном итоге реализован полиморфизм в конечном бинарном образе.
Вернее, это имеет значение, только когда такой полиморфизм реализован ПЛОХО, тогда волей-неволей приходится держать в голове некоторую ЦЕНУ этого полиморфизма. Если же цена реализации близка к 0-лю (в случае С++ или value-type параметров генериков дотнета), то "оно" сразу становится не принципиальным. Ну, т.е. никаких квантов внимания разработчика не потребляет.
S>И если интерпретатор, рантайм, или еще кто выполняет разные версии кода для разных типов, то это ad hoc.
Хороший оптимизатор в итоге и должен сводить подавляющее большинство сценариев к ad hoc.
Но это, повторюсь, лишь подробности реализации компилятора, которые могут быть несколько ортогональны "внешним" возможностям ЯП.
V>>Неверно. Тут в разные годы пробегали примеры на Хаскель с использованием классов типов, где без динамического матчинга времени исполнения никак — компилятор Хаскель создаёт таблицы ф-ий для их инстансов для конкретных типов.
S>Согасен. Для программиста это выглядит статически, а в рантайме может использоваться динамика для избежания распухания, или по каким-то другим мотивам. Статика или динамика не сказывается на универсальности.
Вот.
V>>По-сути мы зацепились вокруг того, что хотя компилятор C# в 99% случаев был бы способен разресолвить параметрический полиморфизм на этапе компиляции (т.е. приведя его к ad hoc через вывод монотипов, прямо как на шаблонах С++), однако же, ради "экономии" размера бинарника всё будет сведено к динамическому ad hoc-полиморфизму на основе виртуальных вызовов типов-ограничений (интерфейсов или абстрактных/виртуальных баз).
S>не вполне распарсил выделенное
Монотип — это конкретный тип.
В общем, ясно. ))
Попробую разобрать всё это нагромождение "городских легенд" вокруг полиморфизма:
Изначально под "типизированностью" понималось оперирование в неких "окончательно определённых типах" в исходнике. Их в Системе F (которую рекламирует Пирс в своей работе "Типы данных") называют "монотипами". Просто термин такой.
А теперь смотри на трансформацию понятия "типизированности". Когда речь о монотипах, то тип по классике — это лишь некое множество (допустимых) значений. Когда же идёт речь о полиморфизме, т.е. об АБСТРАГИРОВАНИИ от конкретных типов, то под "типизированностью" в полиморфизме понимают сочетание некоего "черного ящика" (абстрактного типа, чьё множество значений нам НЕИЗВЕСТНО, мы же именно от этого и абстрагируемся) и допустимых операций над ним. Итого,
под "типизированностью" в абстрактном программировании понимают набор операций над чёрным ящиком, этому набору дано название "концепт". В Системе F (в Хаскеле/ML), дополнительно к списку операций над абстрактным типом есть еще его "открытая" структура (алг.тип, списки и туплы), в свою очередь состоящая из одного или более "концепта".
Итого. Берем обычный ООП-полиморфизм:
foreach(Widget w in widgets)
w.Draw(context);
Список операций над типом задан нам
одним интерфейсом абстрактного класса Widget.
А что делать, если мы хотим задать
более одного списка операций над "черным ящиком"? Вот только в этом месте возникает параметрический полиморфизм в ООП. В генериках дотнета надо будет задать более одного "ограничения".
Ты уже увидел тот прикол, что в случае единственного "ограничения" для генериков дотнета смысла в генериках получается немного? ))
У нас там остаётся всего одна "параметрически полиморфная" операция, помимо заданных в списке операций указанного "ограничения" — это копирование "типизированной" ссылки на объект (т.е. экономия на "обслуживании" приведений типов в аргументах и возвращаемых результатах публичных методов, т.е. на генерации бинарного кода выброса исключения в случае неудачного такого приведения). Остальное не сильно отличается от приведённого примера "обычного ООП-полимофизма".
А увидел ли еще тот прикол, что в случае Системы F для аналогичного приведённому сниппету уже требуется механизм параметрического полиморфизма + единственное ограничение — Wiget? ))
Т.е., прикол в том, что в ООП такой сценарий всё еще НЕ считается параметрическим полиморфизмом, хотя его бинарная механика идентична происходящему в Хаскель в этом же сценарии — в обоих случаях идёт рантайм-матчинг конкретного типа и соответствующей ф-ии Draw по сгенерённой компилятором таблице ф-ий.
Далее. Параметрический полиморфизм в ограничениях называют "типизированным параметрическим полиморфизмом". В этом смысле шаблоны С++ дают технику "нетипизированного параметрического полиморфизма". Однако, перегрузка имен ф-ий в области видимости шаблонного кода (и/или перегрузка операторов) таки дают своего рода "ограничения" в том смысле, что если операции из используемых в теле шаблона найти не удалось, то компиляция будет не успешной. Но "открытость" области видимости представляет из себя некоторую опасность, угу. Держим этот момент в уме.
Дополнительно в шаблонах С++ доступны типы, определённые внутри других типов (непосредственным образом или через typedef), что дополнительно позволяет определять и использовать
отношения м/у типами, где этот механизм с определённой натяжкой можно приравнять к трюку с "открытой структурой" типов в Хаскеле/ML/Системе F. "С натяжкой" — потому что любой матчинг типов должен быть произведён в compile-time. Аналогично с доступом к открытым полям структур (в т.ч. классов и объединений).
Я думаю, всего сказанного должно быть достаточно, чтобы понять, как даже на С++ сделать тот самый "типизированный параметрический полиморфизм". Можно использовать операции с явным указанием области видимости — из специального нейспейса или некоего типа — "словаря операций". Т.е., достаточно лишь "закрыть" область поиска перегруженных ф-ий. Список операций над типом из указанной области видимости и будет обеспечивать ту самую "типизированность" в обобщённом программировании, т.е. задавать однозначность ресолвинга нужной ф-ии. И такая техника тоже используется — например, в контейнеры подаётся специальный "словарь" — allocator<T>, где операции над типизированным выделением/освобождением памяти производятся через такой "словарь". Причём, "словарь" может реализовать как статические методы, так и методы экземпляра. Тут в С++ сильно помогает то, что через имя переменной и точку ( al.alloc() ) можно вызывать не только методы экземпляра, но и статические. Т.е., на самом деле всё вместе получается не так уж и грустно, а местами очень даже удобно.
V>>Разные виды полиморфизма ни в коем случае не ортогональны друг другу, а часто друг через друга выражаются или некие случаи представляют из себя одновременно несколько разновидностей полиморфизма. Поэтому, ИМХО, — это рассуждения вникуда, когда пытаются один вид полиморфизма "тщательно отделить" от другого.
S>Не вполне согласен. Есть чистые случаи (пусть они крайности), есть комбинированные.
Ну вот я тебе дал сниппет ООП-кода. Это тот самый частный случай параметрического полиморфизма, реализованный через динамический ad hoc.
А разве принято, скажем, считать, что в Объектном Паскале присутствует параметрический полиморфизм? )) Вот. Поэтому я лишь скромно советую не забивать себе голову попытками тщательного отделения одной разновидности полиморфизма от другой. Тем более, что хороший оптимизатор запросто умеет сводить одно к другому "унутре" (я тут рядом жаловался на то, что компилятор С++ слишкком часто генерит прямые вызовы вместо виртуальных, распространяя своё "зрение" транзитивно, т.е. и в вызываемом коде опять имеет возможность повторить такой же трюк, коль ранее свёл абстрактный тип к монотипу).
V>>Например, в Хаскеле предопределены некоторые базовые классы типов, необходимые для использования операторов языка: Eq, Ord, Num, Real, Integral, Monad или для базовых операций преобразования в строку — Show. Т.е., совсем "из ничего" не получится.
S>Вот все что я выделил — это https://wiki.haskell.org/Polymorphism#Ad-hoc_polymorphism
S>С этим есть разногласия? А я говорил о параметрическом.
Это всё параметрический полимофизм тоже. ))
Ты прямо "с самого низа" сразу можешь описывать параметрически полиморфные ф-ии, используя уже предопределённые в рамках стандарта языка "ограничения" — классы Eq, Ord, Num, Show и т.д. Т.е., тебе не всегда нужны свои (пользовательские) классы типов.
V>>А вот не надо пытаться тщательно отделять мух от котлет, когда речь о полиморфизме. ))
V>>Потому что это зачастую банально невозможно.
S>Что тут сложного? equal_to — ad hoc, Eq a — ad hoc.
А тут:
equal_to :: (Eq a) => a -> a -> Bool
equal_to a b = (a == b)
?
S>Но если внезапно из рассмотрения выкинуть все типы, кроме тех, у которых определен оператор == или инстанс Eq, то тогда они выглядят как параметрические.
Ну вот я против таких заморачиваний и выступал в прошлом сообщении, что это всё условности. Ты можешь считать так, что моё определение equal_to для Хаскеля — это всегда параметрический полиморфизм. Такой подход можно назвать "строгим", теоретикам навроде Пирса нравится. А тот факт, что Хаскель способен в 99% случаев привести его к ad hoс через оптимизацию и/или вывод типов в конкретном контексте — лишь приятный бонус, эдакий побочный эффект. ))