Здравствуйте, alex_public, Вы писали:
_>Нуу для самых требовательных к производительности задач может быть выгоднее компиляция как раз на конечном устройстве, т.к. можно будет использовать все возможности процессора (а не брать некий минимальный уровень, как принято сейчас). Хотя для мобильных процессоров это пока не столько актуально как для десктопных (где какой-нибудь AVX2 очень заметно круче SSE4), но всё же...
Так можно передать, что умеет процессор, и получить подходящий бинарник, не универсальный, а именно под него. Если мы говорим о мобильных устройствах, то сама компиляция для них — довольно тяжелая задача, особенно для не самых последних-топовых, почему не отдать эту работу серверу?
Сейчас вон научились предлагать по-умолчанию правильный (32/64 бита, win/mac/nix) вариант на странице загрузки, но только в вебе и для PC. А магазин вроде гуглоплея куда большей информацией обладает, не только стандартными http заголовками. Ну а если сервер все же не обладает, легко можно научить клиент ее отсылать.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, samius, Вы писали:
_>>Эээ что? Ты написал, что из моего определения "следует что перегрузка функций — параметрический полиморфизм" (это точная цитата). Вот я тебе и предлагаю привести конкретный пример (перегрузку функций, подходящую под моё определение параметрического полиморфизма), который демонстрировал бы правоту этого твоего тезиса. S>Да я это написал, а ты меня спросил (см выделенное выше). S>А пример, который ты просишь — ровно то, что мы и обсуждаем: equal_to, который единым образом для разных типов вызывает совершенно различный код, определенный в совершенно разных мономорфных функциях.
А, я кажется понял чем вызваны твои странные интерпретации и выводы. Вот смотри, у нас есть некая функция f. А так же функция g, которая вызывает внутри себя функцию g. Правильно ли я понял, что в такой ситуации ты в какой-то степени относишь код функции f к функции g (не говорим сейчас про всякие низкоуровневые вопросы типа инлайнинга и т.п., а обсуждаем только исходные коды)? Если да, то тогда конечно понятно, что ты переносишь особенности реализации функции f на функцию g. Однако в моём понимание к коду функции g в связи с f относится исключительно сама процедура вызова (грубо говоря ассемблерная инструкция call), а как там реализована f вообще не важно.
_>>diff то как раз разницу покажет. А вот любой вменяемый программист скажет что разницы в данных двух примерах кода нет. S>Не уверен. Давай спросим любого вменяемого? Как будем определять вменяемость?
Давай начнём с тебя) Хочешь сказать, что видишь разницу в коде реализации этого примера на Хаскеле и на C++? )
_>>Но меня больше интересует другое. С твоей точки зрения apply из C++ примера — это какой полиморфизм? ) S>именно apply — выглядит параметрически полиморфно так же как и apply в Хаскеле, с той разницей, что применение с ее помощью разных функций приведет к распуханию бинарника и обращениям в рантайме к разным версиям, что указывает на ad hoc.
ОК, т.е. ты в принципе признаёшь возможность нормального параметрического полиморфизма в C++ (тогда наша дискуссия снова возвращается на конструктивное русло из холливарной области), но при этом имеешь возражения в принадлежности к нему конкретного кода (equal_to), правильно?
_>>Тут как раз всё просто. Данный вариант функции apply невозможно засунуть в бинарный модуль (библиотеку), как это сделано в Хаскеле. В то же время конечное приложение, использующий данный (C++) вариант функции apply будет оптимизировано гораздо лучше своего аналога на Хаскеле. S>Оптимизировано в каком отношении? S>И как возможность оптимизации влияет на классификацию?
Я поясняю разницу в реализации Хаскеля и C++. На уровне исходного кода её нет. А на уровне генерируемого машинного разница заметная (обмен возможности модульности на производительность).
S>>>Вроде есть. Но там у нас подобие полиморфизма первого ранга. Так что, ради твоей забавы не стану корячиться. _>>Угу, именно про это я имел в виду, когда писал в предыдущем сообщение "нет ни удобства, ни быстродействие". ))) S>Лично я в плане удобства С++ -у предпочитаю C#. А быстродействие меня (и не только) пока устраивает.
В C# не удобство, а простота (тривиальность). Это большая разница. Если бы было реальное удобство, то не было бы никаких проблем повторить простейший код (записываемый в лоб и на Хаскеле и на C++ и на Python'е и на множестве других языков) из трёх строчек.
Здравствуйте, samius, Вы писали:
S>>>Т.е. я могу согласиться с тем, что equal_to параметрически полиморфна на множестве типов с определенным оператором сравнения _>>Хм, так а вроде только это моё утверждение и вызывало у тебя возражения. Если ты теперь с ним согласился, то: S>вырвал таки из контекста. Я с этим согласен, если из рассмотрения выбросить любые типы, не реализующие оператор ==.
Ну так для других оно и не применяется, так что всё ОК.
Хотя я по прежнему не понимаю почему ты не предложил подобную же оговорку для apply (она же ведь точно так же как и equal_to вызовет ошибку компиляции для некоторых сочетаний типов параметров).
Здравствуйте, Ops, Вы писали:
_>>Нуу для самых требовательных к производительности задач может быть выгоднее компиляция как раз на конечном устройстве, т.к. можно будет использовать все возможности процессора (а не брать некий минимальный уровень, как принято сейчас). Хотя для мобильных процессоров это пока не столько актуально как для десктопных (где какой-нибудь AVX2 очень заметно круче SSE4), но всё же... Ops>Так можно передать, что умеет процессор, и получить подходящий бинарник, не универсальный, а именно под него. Если мы говорим о мобильных устройствах, то сама компиляция для них — довольно тяжелая задача, особенно для не самых последних-топовых, почему не отдать эту работу серверу? Ops>Сейчас вон научились предлагать по-умолчанию правильный (32/64 бита, win/mac/nix) вариант на странице загрузки, но только в вебе и для PC. А магазин вроде гуглоплея куда большей информацией обладает, не только стандартными http заголовками. Ну а если сервер все же не обладает, легко можно научить клиент ее отсылать.
Тогда уж компилировать не на сервере (отдавать все исходники гуглу — так себе идея), а по старинке у разработчика. Те же несколько версий что и сейчас (или даже больше, если говорим ещё и о производительности) и все их загружаем в магазин. А он должен будет уметь выдать нужное по данным о клиентском устройстве. Теоретически никаких проблем в реализации подобного нет. Но кто будет инициировать подобные изменения, если сейчас "и так всё работает"? )
Здравствуйте, alex_public, Вы писали:
_>Тогда уж компилировать не на сервере (отдавать все исходники гуглу — так себе идея), а по старинке у разработчика. Те же несколько версий что и сейчас (или даже больше, если говорим ещё и о производительности) и все их загружаем в магазин.
Я не про исходники говорил, а про промежуточный код. Отдаем в магазин ту же жабку (или, как ты предлагаешь, llvm), а клиентам отдается уже скомпилированный в нейтив бинарник под их платформу. _>А он должен будет уметь выдать нужное по данным о клиентском устройстве. Теоретически никаких проблем в реализации подобного нет. Но кто будет инициировать подобные изменения, если сейчас "и так всё работает"? )
Угу, это-то и печально. Вроде и мир будет лучше, и энтропия будет меньше расти, но те, кто может, в этом не заинтересованы, наколхозили костылей, и ладно.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, samius, Вы писали:
S>>А пример, который ты просишь — ровно то, что мы и обсуждаем: equal_to, который единым образом для разных типов вызывает совершенно различный код, определенный в совершенно разных мономорфных функциях.
_>А, я кажется понял чем вызваны твои странные интерпретации и выводы. Вот смотри, у нас есть некая функция f. А так же функция g, которая вызывает внутри себя функцию g. Правильно ли я понял, что в такой ситуации ты в какой-то степени относишь код функции f к функции g (не говорим сейчас про всякие низкоуровневые вопросы типа инлайнинга и т.п., а обсуждаем только исходные коды)? Если да, то тогда конечно понятно, что ты переносишь особенности реализации функции f на функцию g. Однако в моём понимание к коду функции g в связи с f относится исключительно сама процедура вызова (грубо говоря ассемблерная инструкция call), а как там реализована f вообще не важно.
Хорошо, давай оторвемся от низкоуровневых вопросов, хотя я к ним не тяготел. Но недалеко. Представим, что у нас есть идеальный исполнитель программы на неком языке, у которого нет проблем с раздувом кода, скоростью выполнения и т.п. И задача, не требующая новых типов и специальных приседаний с ними. Только комбинация. Тогда в принципе пофигу на классификацию. Полиморфно и хрен с ним. И тогда я не против твоего понимания того как устроена g, каким образом она обращается к f, сколько реализаций у f, как их g будет вызывать — чихать. Никаких возражений.
А теперь возвращаемся в реальную жизнь. Одна специально полиморфная функция заражает другую, которая параметризуется ей, третью и далее. Кстати, я определение equal_to не рассматриваю как функцию (с адресом вызова). А вот equal_to<int> и equal_to<float> — вполне себе функции с адресами. И когда ты параметризуешь с помощью equal_to transform, то код transform раздувается по числу используемых типов (адресов операторов). Вот Wadler (или Blott) привел тупой пример, где нужна функция, вычисляющая тройку квадратов по тройке значений. Учитывая что каждое из значений может быть либо целое, либо плавающее, требуется 9 перегрузок что бы обслужить все случаи всего лишь для такого тупого примера.
(Не знаю, видел ли ты статью "How to make ad-hoc polymorphism less ad hoc". Погляди по диагонали, она короткая. И она именно о том, почему появились классы типов, какие проблемы они решают. Кстати, там был упомянут подход, при котором равенство для типов должно было быть полностью полиморфным для любого типа (==):: a -> a -> Bool. Что наводит на мысль о том что equal_to все-таки ad hoc)
Так вот, ad hoc — это все о том, как пухнет код при топорном подходе к компиляции. А проблема распухания — она более касается функции g, которая комбинирована f-ом, и вообще, чем больше комбинирования ad-hoc-ов, тем больше разбухание. Т.е. когда Wadler писал об этой проблеме, я полагаю что он подразумевал именно это. Будут другие мысли — охотно выслушаю, но только после прочтения "How to make ad-hoc less ad hoc".
Ну и, собственно, к equal_to — какой бы он не был красивый и удобный для программиста, он не препятствует разбуханию. Все что он делает — удобненько подставляет адресок мономорфного сравнения. Таким образом, специальная природа сравнения расползается по всему коду, который использует этот equal_to (без дополнительной косвенности). В итоге, и transform становится как вирус.
Знал бы Wadler в 88м году что у transform в 2017м будет параметризован 5ю типами...
_>>>diff то как раз разницу покажет. А вот любой вменяемый программист скажет что разницы в данных двух примерах кода нет. S>>Не уверен. Давай спросим любого вменяемого? Как будем определять вменяемость?
_>Давай начнём с тебя) Хочешь сказать, что видишь разницу в коде реализации этого примера на Хаскеле и на C++? )
Ок, если забить на особенности выполнения и компиляции, то для абстрактного исполнителя семантика apply-ев в C++ и Haskell одинакова.
_>ОК, т.е. ты в принципе признаёшь возможность нормального параметрического полиморфизма в C++ (тогда наша дискуссия снова возвращается на конструктивное русло из холливарной области), но при этом имеешь возражения в принадлежности к нему конкретного кода (equal_to), правильно?
Правильно. Я не отрицал возможность нормального параметрического полиморфизма в C++.
S>>И как возможность оптимизации влияет на классификацию?
_>Я поясняю разницу в реализации Хаскеля и C++. На уровне исходного кода её нет. А на уровне генерируемого машинного разница заметная (обмен возможности модульности на производительность).
То есть ты ушел от ответа на вопрос "как" возможность оптимизации влияет на классификацию, а я нечаянно на него ответил (или попытался).
S>>Лично я в плане удобства С++ -у предпочитаю C#. А быстродействие меня (и не только) пока устраивает.
_>В C# не удобство, а простота (тривиальность). Это большая разница. Если бы было реальное удобство, то не было бы никаких проблем повторить простейший код (записываемый в лоб и на Хаскеле и на C++ и на Python'е и на множестве других языков) из трёх строчек.
А мне не нужно повторять простейший код за C++ на C#. У меня почему-то наоборот. Если я захочу повторить 20К C# строк бизнес логики на C++, то получу 40К строк.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, samius, Вы писали:
S>>вырвал таки из контекста. Я с этим согласен, если из рассмотрения выбросить любые типы, не реализующие оператор ==.
_>Ну так для других оно и не применяется, так что всё ОК.
все, кроме расбухания
_>Хотя я по прежнему не понимаю почему ты не предложил подобную же оговорку для apply (она же ведь точно так же как и equal_to вызовет ошибку компиляции для некоторых сочетаний типов параметров).
у apply другая оговорка. Первый параметр должен уметь применяться ко второму. Но при этом это не проблемы apply. По идее грамотный компилятор вообще должен убрать вызов apply и вставить применение первого параметра ко второму. И тогда от apply- ничего не опухнет.
Здравствуйте, samius, Вы писали:
S>Здравствуйте, Serginio1, Вы писали:
S>>Здравствуйте, samius, Вы писали:
S>>>Какая разница, что я передаю, если обычный оператор <, он же переписанный линк реврайтером, он же поданный в виде Expression и скомпилированный в рантайме, все они вызываются одинакого с точностью до инструкции при выполнении? S>> Разница есть. Если Func то вызовется этот метод, а деревья развернутся и по сути проинлайнчятся S>Я говорю об компараторе, а не о делегате.
Угу большая разница между поиском ссылки на метод через интерфейс или через ссылку в Delegate S>>>И? А если бы мы работали с DynamicMethod, то оператор < вызывался бы как-то по другому? S>> DynamicMethod это метод а дерево это дерево. Дерево нужно скомпилировать для выполнения. А в Linq To Sql оно просто собирается и из него строится выражение SQL S>DynamicMethod тоже без компиляции не выполнится.
Ты разницу не чувствуешь между Il генератором и деревьями выражений?
По твоему ДВ просто не нужны, раз есть DynamicMethod?
Весь смысл деревьев, что мы можем их собирать и анализировать
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
S>Здравствуйте, samius, Вы писали:
S>>> Разница есть. Если Func то вызовется этот метод, а деревья развернутся и по сути проинлайнчятся S>>Я говорю об компараторе, а не о делегате. S> Угу большая разница между поиском ссылки на метод через интерфейс или через ссылку в Delegate
Какой еще интерфейс?
S>>> DynamicMethod это метод а дерево это дерево. Дерево нужно скомпилировать для выполнения. А в Linq To Sql оно просто собирается и из него строится выражение SQL S>>DynamicMethod тоже без компиляции не выполнится.
S>Ты разницу не чувствуешь между Il генератором и деревьями выражений? S>По твоему ДВ просто не нужны, раз есть DynamicMethod?
Разве я уточнением того что DM надо тоже компилировать дал понять что не чувствую разницу между генератором IL и ДВ? Откуда следует что по моему ДВ не нужны?
S> Весь смысл деревьев, что мы можем их собирать и анализировать
И как это связано со способом вызова компарера?
Здравствуйте, samius, Вы писали:
_>>А, я кажется понял чем вызваны твои странные интерпретации и выводы. Вот смотри, у нас есть некая функция f. А так же функция g, которая вызывает внутри себя функцию g. Правильно ли я понял, что в такой ситуации ты в какой-то степени относишь код функции f к функции g (не говорим сейчас про всякие низкоуровневые вопросы типа инлайнинга и т.п., а обсуждаем только исходные коды)? Если да, то тогда конечно понятно, что ты переносишь особенности реализации функции f на функцию g. Однако в моём понимание к коду функции g в связи с f относится исключительно сама процедура вызова (грубо говоря ассемблерная инструкция call), а как там реализована f вообще не важно. S>Хорошо, давай оторвемся от низкоуровневых вопросов, хотя я к ним не тяготел. Но недалеко. Представим, что у нас есть идеальный исполнитель программы на неком языке, у которого нет проблем с раздувом кода, скоростью выполнения и т.п. И задача, не требующая новых типов и специальных приседаний с ними. Только комбинация. Тогда в принципе пофигу на классификацию. Полиморфно и хрен с ним. И тогда я не против твоего понимания того как устроена g, каким образом она обращается к f, сколько реализаций у f, как их g будет вызывать — чихать. Никаких возражений.
Ну как бы если размышлять только на уровне исходных кодов, то всё так и есть.
А вот разница в видах полиморфизма на таком уровне как раз очень важна. Потому как если в языке доступен только ad-hoc, то это будет вынуждать программистов заниматься многократным размножением одного и того же исходного кода. Кстати, в таких случая иногда вообще уходят в другую сторону и применяют вместо всего этого технику "стирания типов". Например с помощью void* в C или Object в ранней Java. Это всё или не безопасно или медленно, но в любом случае крайне не удобно. Однако предпочитают использовать даже такое, только чтобы не заниматься "копипастой".
Так что на уровне исходных кодов наличие какой-то разновидности параметрического полиморфизма крайне необходимо. А вот на уровне машинных всё наоборот — там оно по нынешним временам только во вред, но об этом ниже.
S>А теперь возвращаемся в реальную жизнь. Одна специально полиморфная функция заражает другую, которая параметризуется ей, третью и далее. Кстати, я определение equal_to не рассматриваю как функцию (с адресом вызова). А вот equal_to<int> и equal_to<float> — вполне себе функции с адресами. И когда ты параметризуешь с помощью equal_to transform, то код transform раздувается по числу используемых типов (адресов операторов). Вот Wadler (или Blott) привел тупой пример, где нужна функция, вычисляющая тройку квадратов по тройке значений. Учитывая что каждое из значений может быть либо целое, либо плавающее, требуется 9 перегрузок что бы обслужить все случаи всего лишь для такого тупого примера. S>(Не знаю, видел ли ты статью "How to make ad-hoc polymorphism less ad hoc". Погляди по диагонали, она короткая. И она именно о том, почему появились классы типов, какие проблемы они решают. Кстати, там был упомянут подход, при котором равенство для типов должно было быть полностью полиморфным для любого типа (==):: a -> a -> Bool. Что наводит на мысль о том что equal_to все-таки ad hoc) S>Так вот, ad hoc — это все о том, как пухнет код при топорном подходе к компиляции. А проблема распухания — она более касается функции g, которая комбинирована f-ом, и вообще, чем больше комбинирования ad-hoc-ов, тем больше разбухание. Т.е. когда Wadler писал об этой проблеме, я полагаю что он подразумевал именно это. Будут другие мысли — охотно выслушаю, но только после прочтения "How to make ad-hoc less ad hoc". S>Ну и, собственно, к equal_to — какой бы он не был красивый и удобный для программиста, он не препятствует разбуханию. Все что он делает — удобненько подставляет адресок мономорфного сравнения. Таким образом, специальная природа сравнения расползается по всему коду, который использует этот equal_to (без дополнительной косвенности). В итоге, и transform становится как вирус. S>Знал бы Wadler в 88м году что у transform в 2017м будет параметризован 5ю типами...
К данному тексту у меня есть три возражения разной важности:
1. Мелкое замечание. Не очень хороший пример с размножением transform по специализациям применяемой функции. Дело в том, что transform (да и остальные алгоритмы из STL) принимает в качестве параметра как раз не специализированную, а обобщённую функцию (т.е. f, а не f<int>). А специализация происходит уже внутри, в соответствие с типом элементов переданного контейнера. Так что грубо говоря число "копий" transform(..., ..., ..., f) будет в точности равно числу обрабатываемых типов элементов. Т.е. полиморфная природа f никак не влияет на число копий transform.
2. Про неизбежность распухания кода из-за ad-hoc полиморфизма. Ты всё время при подобных рассуждениях держишь в уме реализацию шаблонов в C++, но это же не единственный вариант. Т.е. понятно что в любом языке на самом низком уровне (в конце концов это АЛУ процессора) лежит ad-hoc полиморфизм, над которым выстраиваются слои параметрического полиморфизма (если он вообще есть). В C++ при компиляции все эти слои интегрируются ради быстродействия. Но мы же уже видели на примере отдельной бинарной сборки apply в Хаскеле, что есть и другие реализации. Т.е. при такой реализации низкоуровневый ad-hoc полиморфизм (оператор равенства в нашем примере) легко изолируется на своём уровне и никак не влияет на природу equal_to. Более того, подобное можно элементарно реализовать и на C++, причём даже без всякой переделки компилятора (а вот переписать всю STL придётся). Только оно будет в разы медленнее текущей реализации, так что и задаром никому не нужно.
3. Главное. Даже если забыть в какое время мы живём (когда я скачиваю с сайта фильм в blu-ray качестве (18 ГБ) за 20 минут), то увеличение быстродействия в разы уже давным давно окупает любые распухания кода. Более того, практически все компиляторы (любых языков) имеющие вменяемые оптимизаторы на максимальных уровнях оптимизации увеличивают размер кода. Без всяких шаблонов, полиморфизма и т.п. Просто потому что в основе эффективной оптимизации лежит агрессивный инлайнинг. Т.е. вся индустрия (ну по крайне мере та её часть, где быстродействие хоть что-то значит) стремятся двигаться по этому пути, хотя и не всегда выходит (в том же Хаскеле как только не извращаются, но всё равно выходит медленно), т.к. задача сложная и часто эмпирическая. А в шаблонах C++ это всё даётся с гарантией и так сказать на халяву, по построению. И тут ты говоришь об этом самом распухании как о неком недостатке. Забавно. )
_>>В C# не удобство, а простота (тривиальность). Это большая разница. Если бы было реальное удобство, то не было бы никаких проблем повторить простейший код (записываемый в лоб и на Хаскеле и на C++ и на Python'е и на множестве других языков) из трёх строчек. S>А мне не нужно повторять простейший код за C++ на C#. У меня почему-то наоборот. Если я захочу повторить 20К C# строк бизнес логики на C++, то получу 40К строк.
Это миф. Потому как в C# имеется только один механизм языка увеличивающий его выразительность и отсутствующий в C++ (причём это продлится только до выхода следующего стандарта C++), а в C++ имеется целый набор таких механизмов, отсутствующих в C#. Так что описанное тобой выше явление может встретится на практике только в одном редком случае: когда для C# имеется готовая библиотека предметной области, а для C++ нет. Такое бывает в некоторых узких областях, но как ты понимаешь к дизайну языка это никакого отношения не имеет.
P.S. Последний абзац нашей с тобой дискуссии является полный оффтопиком к самой дискуссии, но при этом на 100% соответствует изначальном сообщению данной темы на форуме. Забавно получилось. )))
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, samius, Вы писали:
S>>Никаких возражений.
_>Ну как бы если размышлять только на уровне исходных кодов, то всё так и есть.
Ок, на уровне исходников пусть так и будет.
_>А вот разница в видах полиморфизма на таком уровне как раз очень важна. Потому как если в языке доступен только ad-hoc, то это будет вынуждать программистов заниматься многократным размножением одного и того же исходного кода. Кстати, в таких случая иногда вообще уходят в другую сторону и применяют вместо всего этого технику "стирания типов". Например с помощью void* в C или Object в ранней Java. Это всё или не безопасно или медленно, но в любом случае крайне не удобно. Однако предпочитают использовать даже такое, только чтобы не заниматься "копипастой". Вот тут, кстати, написано что void* это небезопасный параметрический полиморфизм во весь рост.
_>Так что на уровне исходных кодов наличие какой-то разновидности параметрического полиморфизма крайне необходимо. А вот на уровне машинных всё наоборот — там оно по нынешним временам только во вред, но об этом ниже.
Это к вопросу о том, что считать благом.
S>>Знал бы Wadler в 88м году что у transform в 2017м будет параметризован 5ю типами...
_>К данному тексту у меня есть три возражения разной важности:
_>1. Мелкое замечание. Не очень хороший пример с размножением transform по специализациям применяемой функции. Дело в том, что transform (да и остальные алгоритмы из STL) принимает в качестве параметра как раз не специализированную, а обобщённую функцию (т.е. f, а не f<int>). А специализация происходит уже внутри, в соответствие с типом элементов переданного контейнера. Так что грубо говоря число "копий" transform(..., ..., ..., f) будет в точности равно числу обрабатываемых типов элементов. Т.е. полиморфная природа f никак не влияет на число копий transform.
это понятно. Но число BinaryFunction влияет. Хотя, есть возможность этим управлять, если зарыть в него косвенность через указатель.
_>2. Про неизбежность распухания кода из-за ad-hoc полиморфизма. Ты всё время при подобных рассуждениях держишь в уме реализацию шаблонов в C++, но это же не единственный вариант. Т.е. понятно что в любом языке на самом низком уровне (в конце концов это АЛУ процессора) лежит ad-hoc полиморфизм, над которым выстраиваются слои параметрического полиморфизма (если он вообще есть). В C++ при компиляции все эти слои интегрируются ради быстродействия. Но мы же уже видели на примере отдельной бинарной сборки apply в Хаскеле, что есть и другие реализации. Т.е. при такой реализации низкоуровневый ad-hoc полиморфизм (оператор равенства в нашем примере) легко изолируется на своём уровне и никак не влияет на природу equal_to. Более того, подобное можно элементарно реализовать и на C++, причём даже без всякой переделки компилятора (а вот переписать всю STL придётся). Только оно будет в разы медленнее текущей реализации, так что и задаром никому не нужно.
На природу equal_to не влияет, но раздувает transform.
_>3. Главное. Даже если забыть в какое время мы живём (когда я скачиваю с сайта фильм в blu-ray качестве (18 ГБ) за 20 минут), то увеличение быстродействия в разы уже давным давно окупает любые распухания кода. Более того, практически все компиляторы (любых языков) имеющие вменяемые оптимизаторы на максимальных уровнях оптимизации увеличивают размер кода. Без всяких шаблонов, полиморфизма и т.п. Просто потому что в основе эффективной оптимизации лежит агрессивный инлайнинг. Т.е. вся индустрия (ну по крайне мере та её часть, где быстродействие хоть что-то значит) стремятся двигаться по этому пути, хотя и не всегда выходит (в том же Хаскеле как только не извращаются, но всё равно выходит медленно), т.к. задача сложная и часто эмпирическая. А в шаблонах C++ это всё даётся с гарантией и так сказать на халяву, по построению. И тут ты говоришь об этом самом распухании как о неком недостатке. Забавно. )
Не совсем верно. Я не пытаюсь кому-либо советовать, как правильно реализовывать комбинации полиморфизмов. Я лишь пытаюсь привлечь твое внимание к тому, какие проблемы решали в 88, оговариваясь что Eq a — это ad hoc, но в меньшей мере ad hoc, чем просто перегрузка.
Ты вообще согласен что Eq a — это ad hoc (о чем написано на каждом заборе)? Если да, то как ты объяснишь свое желание считать equal_to параметрическим, хотя он в большей мере ad hoc, о чем писал Wadler?
S>>А мне не нужно повторять простейший код за C++ на C#. У меня почему-то наоборот. Если я захочу повторить 20К C# строк бизнес логики на C++, то получу 40К строк.
_>Это миф. Потому как в C# имеется только один механизм языка увеличивающий его выразительность и отсутствующий в C++ (причём это продлится только до выхода следующего стандарта C++), а в C++ имеется целый набор таких механизмов, отсутствующих в C#. Так что описанное тобой выше явление может встретится на практике только в одном редком случае: когда для C# имеется готовая библиотека предметной области, а для C++ нет. Такое бывает в некоторых узких областях, но как ты понимаешь к дизайну языка это никакого отношения не имеет.
Это не миф, это результат моего опыта. Да, я в разной степени владею C# и C++. И именно в моем случае скорость развития С++ проекта в разы медленнее, чем скорость развития схожего по сложности проекта на C#. Время компиляции тоже имеет значение. Разработка на C++ в моем случае является драйвером обновления рабочего железа. Как перехожу на C#, я на том же железе могу еще лет 5 комфортно работать.
_>P.S. Последний абзац нашей с тобой дискуссии является полный оффтопиком к самой дискуссии, но при этом на 100% соответствует изначальном сообщению данной темы на форуме. Забавно получилось. )))
Да.
Так что, equal_to — ad hoc?
Здравствуйте, Qbit86, Вы писали:
V>>Ну и зачем ты так сильно подставляешься-то? Q>Ты опять за свою риторику?
Если оппонент располагает, почему бы и нет?
У тебя ж язвительность на язвительности, вместо здравого смысла.
Ты не можешь удержаться даже от того, чтобы не "украсить" ею заголовки своих сообщений — эдак тебя колбасит. ))
V>>ни сам твой пример Q>Хорошо, будем рассматривать твой пример.
Да всё плохо.
Нельзя на форуме программистов разыгрывать из себя эдакого Ваню-невежду, заставляя оппонентов делать за тебя твою мыслительную работу.
Не заходит это. Нудятина. Слишком уж удобную позицию занимает Ваня при таких раскладах — сам говорит минимум, но зато поле для "охоты" за высказываниями оппонентов — бескрайнее, угу. Если бы у меня были хоть малейшие подозрения, что ты всю эту херню делаешь специально и прекрасно отдаёшь себе в этом отчет — сразу бы отослал по известному адресу.
V>>Конкретно в случае vector<> достаточен одинаковый размер элементов. Q> Разумеется, одинаковый размер недостаточен.
(Зевая) у тебя там оператор копирования/перемещения не тривиальный в одном из случаев.
Q>Ну то есть при включённой оптимизации распухание обобщённого кода по сравнению с IL (не имеющим такой оптимизации) не сто раз, а всего десять?
Угу, только распухание тут в обратную сторону в сто раз. Самое маленькое дотнетное приложение способно загрузить чудовищные по объемам бинарные (после ngen) зависимости в память. Потому что точные зависимости выяснить невозможно.
Q>Кстати, скажи, как отключить эту и только эту оптимизацию.
MSVC:
/OPT:ICF[=iterations]
/OPT:NOICF
gcc:
-fipa-icf
дополнительно в gold-линкере icf=all
Но всё это хорошо работает только совместно с другими редукциями:
-fipa-pta -fipa-profile -fipa-pure-const -fipa-reference и т.д. и т.п. + сверху девиртуализация вызовов + LTO.
В случае MSVC — аналогично, заметный толк есть только в случае полной оптимизации + LTO.
Т.е. много чего одинакового получается как раз после агрессивного упрощения.
Q>В том примере «статичность» библиотеки непринципиальна, пусть будет распухшая динамическая библиотека.
В моём примере распухшей динамической библиотеки не будет.
Здравствуйте, samius, Вы писали:
_>>А вот разница в видах полиморфизма на таком уровне как раз очень важна. Потому как если в языке доступен только ad-hoc, то это будет вынуждать программистов заниматься многократным размножением одного и того же исходного кода. Кстати, в таких случая иногда вообще уходят в другую сторону и применяют вместо всего этого технику "стирания типов". Например с помощью void* в C или Object в ранней Java. Это всё или не безопасно или медленно, но в любом случае крайне не удобно. Однако предпочитают использовать даже такое, только чтобы не заниматься "копипастой". S>Вот тут, кстати, написано что void* это небезопасный параметрический полиморфизм во весь рост.
Ну да, они с помощью техники стирания типов добавляют параметрический полиморфизм в язык у которого его изначально нет.
Кстати, в C++ имеется своя, полностью безопасная техника стирания типа: http://www.boost.org/doc/libs/1_63_0/doc/html/boost_typeerasure.html. И с помощью неё можно элементарно написать аналог equal_to, который будет демонстрировать параметрический полиморфизм не только на уровне исходного кода, но и на уровне машинного:
В данном случае функция my_equal_to так же работает с любыми типами имеющими оператор равенства, но при этом она полностью изолирует ad-hoc природу этого оператора внутри себя — она имеет единый и исходный и машинный код для всех типов. По сути похоже на работу внутренностей Хаскеля. И такое же медленное (без всякого инлайнинга). Так что в мире C++ этот механизм используется только для очень специфических целей.
_>>2. Про неизбежность распухания кода из-за ad-hoc полиморфизма. Ты всё время при подобных рассуждениях держишь в уме реализацию шаблонов в C++, но это же не единственный вариант. Т.е. понятно что в любом языке на самом низком уровне (в конце концов это АЛУ процессора) лежит ad-hoc полиморфизм, над которым выстраиваются слои параметрического полиморфизма (если он вообще есть). В C++ при компиляции все эти слои интегрируются ради быстродействия. Но мы же уже видели на примере отдельной бинарной сборки apply в Хаскеле, что есть и другие реализации. Т.е. при такой реализации низкоуровневый ad-hoc полиморфизм (оператор равенства в нашем примере) легко изолируется на своём уровне и никак не влияет на природу equal_to. Более того, подобное можно элементарно реализовать и на C++, причём даже без всякой переделки компилятора (а вот переписать всю STL придётся). Только оно будет в разы медленнее текущей реализации, так что и задаром никому не нужно. S>На природу equal_to не влияет, но раздувает transform.
В шаблонах C++ — да. В других реализациях (см. например выше) — необязательно. Т.е. основная моя мысль в том, что указанное распухание кода является свойством (является ли оно ужасной проблемой или же важным преимуществом — это другой вопрос) именно шаблонов C++, а не обязательным следствием взаимодействия параметрического и ad-hoc полиморфизма. В других реализациях ad-hoc часть вполне себе изолируется и никак не влияет на другие уровни.
_>>3. Главное. Даже если забыть в какое время мы живём (когда я скачиваю с сайта фильм в blu-ray качестве (18 ГБ) за 20 минут), то увеличение быстродействия в разы уже давным давно окупает любые распухания кода. Более того, практически все компиляторы (любых языков) имеющие вменяемые оптимизаторы на максимальных уровнях оптимизации увеличивают размер кода. Без всяких шаблонов, полиморфизма и т.п. Просто потому что в основе эффективной оптимизации лежит агрессивный инлайнинг. Т.е. вся индустрия (ну по крайне мере та её часть, где быстродействие хоть что-то значит) стремятся двигаться по этому пути, хотя и не всегда выходит (в том же Хаскеле как только не извращаются, но всё равно выходит медленно), т.к. задача сложная и часто эмпирическая. А в шаблонах C++ это всё даётся с гарантией и так сказать на халяву, по построению. И тут ты говоришь об этом самом распухании как о неком недостатке. Забавно. ) S>Не совсем верно. Я не пытаюсь кому-либо советовать, как правильно реализовывать комбинации полиморфизмов. Я лишь пытаюсь привлечь твое внимание к тому, какие проблемы решали в 88, оговариваясь что Eq a — это ad hoc, но в меньшей мере ad hoc, чем просто перегрузка. S>Ты вообще согласен что Eq a — это ad hoc (о чем написано на каждом заборе)? Если да, то как ты объяснишь свое желание считать equal_to параметрическим, хотя он в большей мере ad hoc, о чем писал Wadler?
Eq a в данном примере является аналогом не equal_to, а всему множеству операторов равенства в данном C++ проекте. ))) А прямой аналог equal_to вряд ли встретится на практике (хотя пишется элементарно) в Хаскеле и ему подобных языках, просто потому что в отличие от C++ данное мелкое украшательство может оказаться не бесплатным. Кстати, бесплатные многоуровневые абстракции — это как раз одна из ключевых особенностей современного C++.
_>>Это миф. Потому как в C# имеется только один механизм языка увеличивающий его выразительность и отсутствующий в C++ (причём это продлится только до выхода следующего стандарта C++), а в C++ имеется целый набор таких механизмов, отсутствующих в C#. Так что описанное тобой выше явление может встретится на практике только в одном редком случае: когда для C# имеется готовая библиотека предметной области, а для C++ нет. Такое бывает в некоторых узких областях, но как ты понимаешь к дизайну языка это никакого отношения не имеет. S>Это не миф, это результат моего опыта. Да, я в разной степени владею C# и C++. И именно в моем случае скорость развития С++ проекта в разы медленнее, чем скорость развития схожего по сложности проекта на C#.
А, ну если ты только про себя говоришь, то всё может быть. Более знакомым инструментом частенько можно добиться лучших результатов. Даже если сам инструмент хуже по своей природе. )
S>Время компиляции тоже имеет значение. Разработка на C++ в моем случае является драйвером обновления рабочего железа. Как перехожу на C#, я на том же железе могу еще лет 5 комфортно работать.
Это справедливо. )
S>Так что, equal_to — ad hoc?
Которая std::equal_to? На уровне исходных кодов — нет. На уровне машинных — пожалуй. Точнее на уровне машинных кодов её не будет вовсе, а будет только вставленный на место её вызова код оператора равенства (который ad hoc). Я вроде это всё сказал ещё в самом начале дискуссии.
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, samius, Вы писали:
_>Ну да, они с помощью техники стирания типов добавляют параметрический полиморфизм в язык у которого его изначально нет.
угу
_>Кстати, в C++ имеется своя, полностью безопасная техника стирания типа: http://www.boost.org/doc/libs/1_63_0/doc/html/boost_typeerasure.html. И с помощью неё можно элементарно написать аналог equal_to, который будет демонстрировать параметрический полиморфизм не только на уровне исходного кода, но и на уровне машинного:
вот тут я по поводу машинного не согласен.
Ты Вадлера88 посмотрел? Ввел он типы классов, перестало быть сравнение ad hoc? Нет, статья прям и называется "Как из ad hoc сделать меньше ad hoc". Т.е. типы классов в Хаскеле — ad hoc, как ни крути, но в меньшей степени, чем перегрузка. И это не я придумал.
_>В данном случае функция my_equal_to так же работает с любыми типами имеющими оператор равенства, но при этом она полностью изолирует ad-hoc природу этого оператора внутри себя — она имеет единый и исходный и машинный код для всех типов. По сути похоже на работу внутренностей Хаскеля. И такое же медленное (без всякого инлайнинга). Так что в мире C++ этот механизм используется только для очень специфических целей.
Я верю тебе что transform перестает умножаться в таком случае. Но для float и int будет вызываться все же одна машинная инструкция? Или все-таки разные за счет косвенности? Если разные — значит ad hoc (см Wadler88).
S>>На природу equal_to не влияет, но раздувает transform.
_>В шаблонах C++ — да. В других реализациях (см. например выше) — необязательно. Т.е. основная моя мысль в том, что указанное распухание кода является свойством (является ли оно ужасной проблемой или же важным преимуществом — это другой вопрос) именно шаблонов C++, а не обязательным следствием взаимодействия параметрического и ad-hoc полиморфизма. В других реализациях ad-hoc часть вполне себе изолируется и никак не влияет на другие уровни.
Согласен. Но с оговоркой что ad-hoc не перестает быть ad-hoc-ом.
S>>Ты вообще согласен что Eq a — это ad hoc (о чем написано на каждом заборе)? Если да, то как ты объяснишь свое желание считать equal_to параметрическим, хотя он в большей мере ad hoc, о чем писал Wadler?
_>Eq a в данном примере является аналогом не equal_to, а всему множеству операторов равенства в данном C++ проекте. ))) А прямой аналог equal_to вряд ли встретится на практике (хотя пишется элементарно) в Хаскеле и ему подобных языках, просто потому что в отличие от C++ данное мелкое украшательство может оказаться не бесплатным. Кстати, бесплатные многоуровневые абстракции — это как раз одна из ключевых особенностей современного C++.
Я имел в виду аналог семантический, а не по реализации. В плане реализации, видимо, Eq a более смахивает на bool my_equal_to, если я правильно понял.
А бесплатного ничего не бывает.
S>>Это не миф, это результат моего опыта. Да, я в разной степени владею C# и C++. И именно в моем случае скорость развития С++ проекта в разы медленнее, чем скорость развития схожего по сложности проекта на C#.
_>А, ну если ты только про себя говоришь, то всё может быть. Более знакомым инструментом частенько можно добиться лучших результатов. Даже если сам инструмент хуже по своей природе. )
Исключительно про себя.
S>>Так что, equal_to — ad hoc?
_>Которая std::equal_to? На уровне исходных кодов — нет. На уровне машинных — пожалуй. Точнее на уровне машинных кодов её не будет вовсе, а будет только вставленный на место её вызова код оператора равенства (который ad hoc). Я вроде это всё сказал ещё в самом начале дискуссии.
Ок. Осталось выяснить, есть ли вообще в природе классификация полиморфизма, отталкивающаяся от исходных кодов, а не от необходимости выполнения специального кода для разных типов при одинаковой записи.
Здравствуйте, 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с через оптимизацию и/или вывод типов в конкретном контексте — лишь приятный бонус, эдакий побочный эффект. ))
Здравствуйте, vdimas, Вы писали:
V>сам говорит минимум, но зато поле для "охоты" за высказываниями оппонентов — бескрайнее, угу.
«Если оппонент располагает, почему бы и нет?» Елозишь, как вошь на гребешке. На каждый контрпример к твоим сомнительным утверждениям придумываешь всё больше оговорок, о которых почему-то сразу забыл упомянуть.
Q>>:) Разумеется, одинаковый размер недостаточен. V>(Зевая) у тебя там оператор копирования/перемещения не тривиальный в одном из случаев.
Разве моё утверждение было похоже на вопрос? Я просто констатировал медицинский факт, что твоё уверждение «Достаточен одинаковый размер» — неверно.
Здравствуйте, vdimas, Вы писали: V>Здравствуйте, samius, Вы писали: V>Если речь о пользователях языка, а не о разработчиках компилятора с этого языка, то всем им (нам) должно быть одинаково. ))
Ох, всех подписал, и кому чего должно! S>>верно. Но цитата из Пирса однозначно отсылает к механике выполнения в рантайме. V>Не ведись на это. )) V>Хороший теоретик не обязан быть хорошим инженером, и обратное тоже верно.
Т.е у хороших инженеров свой полиморфизм и определения? V>Не имеет никакой разницы, как в конечном итоге реализован полиморфизм в конечном бинарном образе. V>Вернее, это имеет значение, только когда такой полиморфизм реализован ПЛОХО, тогда волей-неволей приходится держать в голове некоторую ЦЕНУ этого полиморфизма. Если же цена реализации близка к 0-лю (в случае С++ или value-type параметров генериков дотнета), то "оно" сразу становится не принципиальным. Ну, т.е. никаких квантов внимания разработчика не потребляет.
Да не нужны там кванты внимания для этого понимания. S>>И если интерпретатор, рантайм, или еще кто выполняет разные версии кода для разных типов, то это ad hoc. V>Хороший оптимизатор в итоге и должен сводить подавляющее большинство сценариев к ad hoc. V>Но это, повторюсь, лишь подробности реализации компилятора, которые могут быть несколько ортогональны "внешним" возможностям ЯП.
Зачем сводить к ad hoc то, что не ad hoc? V>>>По-сути мы зацепились вокруг того, что хотя компилятор C# в 99% случаев был бы способен разресолвить параметрический полиморфизм на этапе компиляции (т.е. приведя его к ad hoc через вывод монотипов, прямо как на шаблонах С++), однако же, ради "экономии" размера бинарника всё будет сведено к динамическому ad hoc-полиморфизму на основе виртуальных вызовов типов-ограничений (интерфейсов или абстрактных/виртуальных баз). S>>не вполне распарсил выделенное V>Монотип — это конкретный тип.
упс
V>В общем, ясно. )) V>Попробую разобрать всё это нагромождение "городских легенд" вокруг полиморфизма:
V>Изначально под "типизированностью" понималось оперирование в неких "окончательно определённых типах" в исходнике. Их в Системе F (которую рекламирует Пирс в своей работе "Типы данных") называют "монотипами". Просто термин такой.
V>А теперь смотри на трансформацию понятия "типизированности". Когда речь о монотипах, то тип по классике — это лишь некое множество (допустимых) значений. Когда же идёт речь о полиморфизме, т.е. об АБСТРАГИРОВАНИИ от конкретных типов, то под "типизированностью" в полиморфизме понимают сочетание некоего "черного ящика" (абстрактного типа, чьё множество значений нам НЕИЗВЕСТНО, мы же именно от этого и абстрагируемся) и допустимых операций над ним. Итого, под "типизированностью" в абстрактном программировании понимают набор операций над чёрным ящиком, этому набору дано название "концепт". В Системе F (в Хаскеле/ML), дополнительно к списку операций над абстрактным типом есть еще его "открытая" структура (алг.тип, списки и туплы), в свою очередь состоящая из одного или более "концепта".
V>Итого. Берем обычный ООП-полиморфизм: V>
V>foreach(Widget w in widgets)
V> w.Draw(context);
V>
V>Список операций над типом задан нам одним интерфейсом абстрактного класса Widget.
V>А что делать, если мы хотим задать более одного списка операций над "черным ящиком"? Вот только в этом месте возникает параметрический полиморфизм в ООП. В генериках дотнета надо будет задать более одного "ограничения".
V>Ты уже увидел тот прикол, что в случае единственного "ограничения" для генериков дотнета смысла в генериках получается немного? )) V>У нас там остаётся всего одна "параметрически полиморфная" операция, помимо заданных в списке операций указанного "ограничения" — это копирование "типизированной" ссылки на объект (т.е. экономия на "обслуживании" приведений типов в аргументах и возвращаемых результатах публичных методов, т.е. на генерации бинарного кода выброса исключения в случае неудачного такого приведения). Остальное не сильно отличается от приведённого примера "обычного ООП-полимофизма".
V>А увидел ли еще тот прикол, что в случае Системы F для аналогичного приведённому сниппету уже требуется механизм параметрического полиморфизма + единственное ограничение — Wiget? ))
V>Т.е., прикол в том, что в ООП такой сценарий всё еще НЕ считается параметрическим полиморфизмом, хотя его бинарная механика идентична происходящему в Хаскель в этом же сценарии — в обоих случаях идёт рантайм-матчинг конкретного типа и соответствующей ф-ии Draw по сгенерённой компилятором таблице ф-ий.
V>Далее. Параметрический полиморфизм в ограничениях называют "типизированным параметрическим полиморфизмом". В этом смысле шаблоны С++ дают технику "нетипизированного параметрического полиморфизма". Однако, перегрузка имен ф-ий в области видимости шаблонного кода (и/или перегрузка операторов) таки дают своего рода "ограничения" в том смысле, что если операции из используемых в теле шаблона найти не удалось, то компиляция будет не успешной. Но "открытость" области видимости представляет из себя некоторую опасность, угу. Держим этот момент в уме.
V>Дополнительно в шаблонах С++ доступны типы, определённые внутри других типов (непосредственным образом или через typedef), что дополнительно позволяет определять и использовать отношения м/у типами, где этот механизм с определённой натяжкой можно приравнять к трюку с "открытой структурой" типов в Хаскеле/ML/Системе F. "С натяжкой" — потому что любой матчинг типов должен быть произведён в compile-time. Аналогично с доступом к открытым полям структур (в т.ч. классов и объединений).
V>Я думаю, всего сказанного должно быть достаточно, чтобы понять, как даже на С++ сделать тот самый "типизированный параметрический полиморфизм". Можно использовать операции с явным указанием области видимости — из специального нейспейса или некоего типа — "словаря операций". Т.е., достаточно лишь "закрыть" область поиска перегруженных ф-ий. Список операций над типом из указанной области видимости и будет обеспечивать ту самую "типизированность" в обобщённом программировании, т.е. задавать однозначность ресолвинга нужной ф-ии. И такая техника тоже используется — например, в контейнеры подаётся специальный "словарь" — allocator<T>, где операции над типизированным выделением/освобождением памяти производятся через такой "словарь". Причём, "словарь" может реализовать как статические методы, так и методы экземпляра. Тут в С++ сильно помогает то, что через имя переменной и точку ( al.alloc() ) можно вызывать не только методы экземпляра, но и статические. Т.е., на самом деле всё вместе получается не так уж и грустно, а местами очень даже удобно.
Ты, конечно, извини. Я дважды честно прочитал, но комментировать это все не буду. Местами я согласен, но в остальном — не хочу еще больше флейма, чем мы имеем с таким простым вопросом, как equal_to. V>Ну вот я тебе дал сниппет ООП-кода. Это тот самый частный случай параметрического полиморфизма, реализованный через динамический ad hoc. V>А разве принято, скажем, считать, что в Объектном Паскале присутствует параметрический полиморфизм? )) Вот. Поэтому я лишь скромно советую не забивать себе голову попытками тщательного отделения одной разновидности полиморфизма от другой. Тем более, что хороший оптимизатор запросто умеет сводить одно к другому "унутре" (я тут рядом жаловался на то, что компилятор С++ слишкком часто генерит прямые вызовы вместо виртуальных, распространяя своё "зрение" транзитивно, т.е. и в вызываемом коде опять имеет возможность повторить такой же трюк, коль ранее свёл абстрактный тип к монотипу).
Ничего я не забиваю. У меня все стройно, а у тебя какая-то каша, забивающая голову. V>>>Например, в Хаскеле предопределены некоторые базовые классы типов, необходимые для использования операторов языка: Eq, Ord, Num, Real, Integral, Monad или для базовых операций преобразования в строку — Show. Т.е., совсем "из ничего" не получится. S>>Вот все что я выделил — это https://wiki.haskell.org/Polymorphism#Ad-hoc_polymorphism S>>С этим есть разногласия? А я говорил о параметрическом. V>Это всё параметрический полимофизм тоже. ))
Ты не читал и не согласен, или читал но не согласен, т.к. вики хаскеля писали сантехники для домработниц, а не инженеры? V>Ты прямо "с самого низа" сразу можешь описывать параметрически полиморфные ф-ии, используя уже предопределённые в рамках стандарта языка "ограничения" — классы Eq, Ord, Num, Show и т.д. Т.е., тебе не всегда нужны свои (пользовательские) классы типов.
Давай я тебе процитирую, что бы ты не пропустил.
You can recognise the presence of ad-hoc polymorphism by looking for constrained type variables: that is, variables that appear to the left of =>, like in elem :: (Eq a) => a -> [a] -> Bool. Note that lookup :: (Eq a) => a -> [(a,b)] -> Maybe b exhibits both parametric (in b) and ad-hoc (in a) polymorphism.
Это по ссылке выше, если что.
То есть, в моем понимании, если ты делаешь в интерпретаторе :t foo и тебе пишут (Eq a) =>, то foo ad hoc полиморфна по типу a.
Просто, не правда ли?
V>>>А вот не надо пытаться тщательно отделять мух от котлет, когда речь о полиморфизме. )) V>>>Потому что это зачастую банально невозможно. S>>Что тут сложного? equal_to — ad hoc, Eq a — ad hoc. V>А тут: V>
V>equal_to :: (Eq a) => a -> a -> Bool
V>equal_to a b = (a == b)
V>
V>?
см выше. Ad hoc. S>>Но если внезапно из рассмотрения выкинуть все типы, кроме тех, у которых определен оператор == или инстанс Eq, то тогда они выглядят как параметрические. V>Ну вот я против таких заморачиваний и выступал в прошлом сообщении, что это всё условности. Ты можешь считать так, что моё определение equal_to для Хаскеля — это всегда параметрический полиморфизм. Такой подход можно назвать "строгим", теоретикам навроде Пирса нравится. А тот факт, что Хаскель способен в 99% случаев привести его к ad hoс через оптимизацию и/или вывод типов в конкретном контексте — лишь приятный бонус, эдакий побочный эффект. ))
да не, я как раз считаю что твое определение equal_to для Хаскеля — это ad hoc. И этого не спрятать 10ю обертками equal_to_10 над equal_to_N. Можно попытаться сотней, но я не уверен что получится.
Здравствуйте, Qbit86, Вы писали:
Q>>> Разумеется, одинаковый размер недостаточен. V>>(Зевая) у тебя там оператор копирования/перемещения не тривиальный в одном из случаев. Q>Разве моё утверждение было похоже на вопрос? Я просто констатировал медицинский факт, что твоё уверждение «Достаточен одинаковый размер» — неверно.
ОК, неверно.
Ну так ты должен был применить конструктивную критику, типа такой: "дополню, что должен быть идентичный код оператора копирования".
Вместо этого фигней маешься, пытаешься "схватить за руку".
Цель-та какова? Разобраться с материалом или поиграть в эти игры?
Здравствуйте, vdimas, Вы писали:
V>Вместо этого фигней маешься, пытаешься "схватить за руку".
Мнительный ты какой-то. Я делаю вполне безобидные утверждения, сопровождаю примерами. Ты же стремишься уличить в некомпетентности, глумиться над невеждами и всё такое.
V>Цель-та какова? Разобраться с материалом или поиграть в эти игры?
Только ты провоцируешь «эти игры» в треде. Твоё поведение токсично, разберись с этим.
Здравствуйте, samius, Вы писали:
_>>Кстати, в C++ имеется своя, полностью безопасная техника стирания типа: http://www.boost.org/doc/libs/1_63_0/doc/html/boost_typeerasure.html. И с помощью неё можно элементарно написать аналог equal_to, который будет демонстрировать параметрический полиморфизм не только на уровне исходного кода, но и на уровне машинного: S>вот тут я по поводу машинного не согласен. S>Ты Вадлера88 посмотрел? Ввел он типы классов, перестало быть сравнение ad hoc? Нет, статья прям и называется "Как из ad hoc сделать меньше ad hoc". Т.е. типы классов в Хаскеле — ad hoc, как ни крути, но в меньшей степени, чем перегрузка. И это не я придумал.
В смысле не согласен? Машинный код одинаков для всех типов с точностью до бита (эту функции в отличие от шаблонной можно скомпилировать в отдельный бинарный модуль). Это просто факт, с которым ничего нельзя поделать.
Если же ты снова намекаешь на то, что данная реализация функции определена не для всех возможных типов (а только для имеющих оператор равенства), то это ничего не меняет. Потому что только для таких типов эта функция и будет использоваться — других вариантов (которые теоретически могли бы создать тот самый ad hoc через перегрузку) не будет в принципе.
_>>В данном случае функция my_equal_to так же работает с любыми типами имеющими оператор равенства, но при этом она полностью изолирует ad-hoc природу этого оператора внутри себя — она имеет единый и исходный и машинный код для всех типов. По сути похоже на работу внутренностей Хаскеля. И такое же медленное (без всякого инлайнинга). Так что в мире C++ этот механизм используется только для очень специфических целей. S>Я верю тебе что transform перестает умножаться в таком случае. Но для float и int будет вызываться все же одна машинная инструкция? Или все-таки разные за счет косвенности? Если разные — значит ad hoc (см Wadler88).
Естественно разные, но это всё будет происходить не в теле функции my_equal_to, так что не имеет к ней отношения. Если же ты будешь настаивать в своём определение на учёт внутреннего устройства ещё и всех других функций (вызываемых из обсуждаемой), то тогда у тебя автоматически получится, что вокруг абсолютно всё ad hoc полиморфизм, а параметрического не существует вовсе (потому что на самом низком уровне всё ad hoc).
Более того, при таком подходе у тебя возникает противоречие с собственными же предыдущими утверждениями. Например ты признал что обсуждаемая ранее функция apply (в Хаскеле) — это чистый параметрический полиморфизм без всяких оговорок. Но ведь в том примере в зависимости от типа второго параметра apply (int или float) выполнялся разный код (если смотреть на весь стек вызова, а не только на само тело apply). Т.е. по твоему подходу выше та apply — это тоже ad hoc.
Похоже что ты не смотря на готовность привести различные цитаты других специалистов так и не сумел сформировать у себя в голове непротиворечивое определение для данного явления.
_>>Eq a в данном примере является аналогом не equal_to, а всему множеству операторов равенства в данном C++ проекте. ))) А прямой аналог equal_to вряд ли встретится на практике (хотя пишется элементарно) в Хаскеле и ему подобных языках, просто потому что в отличие от C++ данное мелкое украшательство может оказаться не бесплатным. Кстати, бесплатные многоуровневые абстракции — это как раз одна из ключевых особенностей современного C++. S>Я имел в виду аналог семантический, а не по реализации. В плане реализации, видимо, Eq a более смахивает на bool my_equal_to, если я правильно понял.
Аналогом my_equal_to в мире Хаскеля будет такой очевидный код:
my_equal_to a b = a==b
Причём данные реализации (C++ и Хаскель) не только выглядят одинаково на уровне исходного кода (и даже имеют по сути одинаковые типы), но и будут крайне похожи на уровне машинного.
S>А бесплатного ничего не бывает.
Бесплатно в смысле производительности и размера программы. Т.е. все эти абстракции живут только на уровне исходного кода, а в машинный не попадают в принципе. Ну а ценой этого можно назвать разве что более долгое (по сравнению с другими языками) время компиляции.
S>>>Так что, equal_to — ad hoc? _>>Которая std::equal_to? На уровне исходных кодов — нет. На уровне машинных — пожалуй. Точнее на уровне машинных кодов её не будет вовсе, а будет только вставленный на место её вызова код оператора равенства (который ad hoc). Я вроде это всё сказал ещё в самом начале дискуссии. S>Ок. Осталось выяснить, есть ли вообще в природе классификация полиморфизма, отталкивающаяся от исходных кодов, а не от необходимости выполнения специального кода для разных типов при одинаковой записи.
Выше мы уже выяснили, что если отталкиваться от твоей особенной классификации (которая хочет учитывать наличие специализации кода на всю глубину стека вызовов), то абсолютно всё вокруг является ad hoc полиморфизмом. )))