Здравствуйте, vdimas, Вы писали:
V>Так я и не для твоего решения предлагал, а для гипотетического автоматического.
Если из моего решения сделать "автоматическое" — поддержка джита точно так же не понадобится (кроме той, что уже есть).
V>В дотнете иногда используется разметка типов через поддержку интерфейсов-атрибутов с нулевым кол-вом методов в нем, вместо собсно дотнетных атрибутов. Банально быстрее в рантайм.
А смысл? Словари никто в рантайме не ищет, только в компайлтайм.
V>А да. В плюсах более менее полноценно поддерживается через template-template, в Swift пока ограничено — можно наследоваться от параметра генерика, т.е. в некоторых сценариях выкрутиться можно. На генериках дотнета принципиально нельзя, ес-но, ведь для такого трюка нужна та самая "текстовая развертка" как ты её обозвал.
Нет, не нужна. В хаскеле, скале и других для этого текстовая развертка не требуется. Ее, в принципе, даже в шарп можно добавить без изменения CLR при отказе от возможности обобщать конструкторы value-типов, что, прямо скажем, очень экзотический юзкейс.
V>Ладно, конкретно с этим спорить надоело. V>1. Я не приемлю повторного определения этого словаря (ведь в первый раз уже определили глобальные операторы).
Т.е. классы типов вы не примемлете в принципе. Они ведь всегда это требуют (в некоторых случаях, правда "мономорфные" операции не определены, сразу с инстанса и начинают).
V>2. Не приемлю ручное задание соответствия типа и словаря.
Это мне самому не нравится, хотя у ручной подстановки есть как некоторые плюсы по сравнению с автоподстановкой, так и реальные сторонники среди хаскелистов http://www.haskellforall.com/2012/05/scrap-your-type-classes.html
V>3. не приемлю AST-синтаксис арифметических выражений: d.mul(d.add(var1, var2), var3).
Это безоговорочно плохо, но подавляющее большинство имен в дотнетных либах — альфанумерик и эта проблема касается мало чего кроме арифметики.
V>Принципиальную возможность определения и использования словаря в дотнете я и не отрицал. Просто эта техника не стала популярной именно из-за перечисленных ограничений. Именно поэтому тебе даже опытные разработчики на дотнете привели необобщенное решение (тело решения было необобщенным), потому что у них уже мозжечок так натренирован. Увы.
Думаю, что причина малого использования — то, что стандартная библиотека реализована не в такой технике. Обычно именно она и диктует, каким должен быть "идеоматический" код. Перечисленные недостатки имеются в том или ином составе во множестве языков и реализаций и как-то преодолеваются программистами (программистам ко всяким проблемам на ровном месте не привыкать, их преодоление они часто, наоборот, считают за доблесть). К примеру, существуют программистские культуры, которые неприемлют операторы в принципе, а значит 3 для них не недостаток, существуют сообщества вокруг языков, где практикуется ручная подстановка словаря в той или иной форме (всякие MLи), а "недостаток" 1 существует во всех реализациях без исключения.
V>Что-то я не вижу в базовой библиотеке типа System.Numerics.ComplexNumber. Может, не туда смотрю?
В базовой библиотеке и Complex не так давно появился. А небазовую библиотеку кто угодно может написать. Главное, что это можно в библиотеку положить, а не писать всякий раз по месту, как лямбды в соседнем примере с Aggregate.
V>Я опять и снова НЕ ПОНИМАЮ, что ты так к этому подтипированию прицепился и что тут плохого.
Просто подтипирование это действительно плохо. Что проявляется на многих уровнях, от стимулирования написания полудинамического глюкокода до проблем со взрывающими мозг ко/контравариантностями (и соотвествующими багами, вроде дыры в дотнет-массивах) и выводом/реконструкцией типов.
Фан факт: чтоб сделать хаскель полностью непригодным для функционального программирования — в него достаточно сабтайпинг добавить и все, больше ничего делать не нужно.
V>В ООП интерфейсы используются как контракт типа. Приведение типа к интерфейсу нужно далеко не всегда, а только лишь в тех случаях, когда требуется рантайм-полиморфизм.
Что характерно, даже для рантайм полиморфизма "приведение к интерфейсу" не нужно — если экзистенциальные типы есть.
V>Это игра терминов. Происходящее фактически идентично. Разница лишь в том, чему именно принадлежат эти таблицы и как формируются.
В том и дело. Разница между "можно заинлайнить в 99% случаев" и "нельзя заинлайнить в 99% случаев" одной "игрой терминов" не достигается.
V>В случае value-type мы имеем один уровень иерархии, так что граблей не много.
А в случае class — имеем не один уровень.
V>Ага, ну так бы сразу и сказал, что нужно по-возможности избегать парадигмы ООП. ))) V>Я бы даже не пытался возражать в эту сторону. )))
Под ООП люди что угодно могут подразумевать, так что я выразился конкретнее.
V>Если бы словарь не надо было организовывать в виде вспомогательного (по-сути, фиктивного) типа, то был бы эквивалент. Если в С++ введут полноценные ограничения на аргументы шаблонов, то эквивалент будет возможно реализовать на С++.
Так ведь словарь и в хаскеле реализован с помощью "вспомогательного фиктивного типа", что в имплементациях где дикшнари пассинг, что в тех где для этого GADT применяют. Поэтому и аналог.
V>У нас на С++ только целевые классы обычно не обобщенные, но сами они практически целиком и полностю построены как комбинация обобщенных кубиков низлежащего уровня. Что мы делаем не так?
Все же уровень не сравнимый. В плюсах обобщение явное и синтаксически тяжеловесное. В ФЯ обобщение по умолчанию.
K>>Нет, так не делается, потому что разделения на тип и словарь нет.
V>Есть, конечно.
Ну да, делается в том числе и так, но это более позднее изобретение. Первоначальный ООП-подход предполагает, что словарь и данные — вместе.
V>В чем суть этих проблем?
В отсутствии раздельной компиляции.
V>Если бы это был первый такой интерфейс — то возражение понятно. Но в дотнете есть неразрывная связь м/у многими типами, компилятором и нижележащей средой исполнения. Так что, этот суп ты уже ничем не испортишь.
Какие-то интерфейсы, конечно, будут волшебными. Я о том, что для нормальной работы все подряд волшебным не сделать.
V>Даже в дотнете это делается через области видимости, через технологию методов-расширений. Сами методы-расширения могут быть генериками.
Нет, полиморфизм на методах расширений не сделать. Реальная альтернатива описываемому подходу — виртуальные методы.
V>Просто в дотнете, опять же, полно ограничений в методах-расширениях. Например, те же переопределённые операторы — тю-тю.
Это не самая серьезная проблема с ними.
K>>Несколько словарей для одного типа — это как раз довольно спорное решение. тут есть трейдофф между когерентностью инстансов и модульностью подхода. В том же хаскеле выбрали первое, а в скале — второе. V>Ну вот... а в С++ и Swift можно использовать любой подход по-вкусу. В общем, всё будет зависеть от дизайна конкретной либы.
Как в С++ можно когерентность инстансов организовать?
V>Для программиста происходящее прозрачно, он по-прежнему оперирует простым инт, поэтому не вижу ничего странного.
И где повод назвать этот инт "встроенным"? всеми остальными объектами он тоже оперирует, так же как этим интом.
V>Просто речь о том, простые типы могут участвовать в сценариях, где их выбор происходит в рантайм.
Не понял, о чем идет речь.
V>В этом смыслу классы типов ну ОЧЕНЬ похожи на интерфейсы ООП.
У вас, по-моему, далекое от реальности представление о классах типов.
Да, без всякой оптимизации класс типов это та же структура с ссылками на функции. Но разница в том, что для класса типов мы обычно уже в компайл тайм знаем, что это за функции. Именно потому инлайн и возможен (хотя для инлайна еще требуется доступность того, что инлайнится). В случае же ООП мы наоборот как правило не знаем в компайл-тайм на какие функции там ссылки, доступны эти функции для инлайна или нет — уже не важно, нам не известно что инлайнить.
V>В случае Хаскеля мы имеем "автосуммы" допустимых типов выражений прямо в точке вызова некоей ф-ии, описанной в классе типов. Компилятор лепит там рантайм-ПМ прозрачно для программиста.
Нет, никакой автосуммы типов и рантайм-ПМ по ней в точке вызова функции-члена класса нет. Компилятор подставляет вместо члена класса код специализированной функции в лучшем случае и ссылку на специализированную функцию в худшем.
V>Известно только если компилятор не видит точный (конечный) тип объекта. Если же видит, то вызывает виртуальные методы напрямую, без таблицы. Именно поэтому я вижу тут аналогию.
Но компилятор обычно не видит конечный тип объекта. И из-за сабтайпинга в том числе, кстати.
V>Во-во. V>Например, там где в Хаскель ЛЮБЫМИ ср-ва языка необходимо было бы выразить последовательность Pet a, там в любом случае поимел бы динамическую диспетчеризаию в стиле ООП. А там, где эта полиморфизм времени исполнения не требуется, там и в ООП прекрасно обходятся без абстрактных классов/интерфейсов.
В ООП сложно обозначить область где рантайм-полиморфизм не требуется. Крайне сложно выявить ее статическим анализом. Виртуальные вызовы только какой-нибудь трассирующий джит нормально устранить сможет.
Поэтому, там где рантайм полиморфизм требуется — там он везде будет. А там, где не требуется — в ООП скорее всего будет, а в хаскеле — нет.
K>>Т.е. одно и то же языковое средство работает и как плюсовой шаблон пока может, и как ООП-класс с виртуальными методами, когда без динамической диспетчеризации действительно не обойтись.
V>а в случае vector<Cat> — этапа компиляции.
Что достигается тем, что параметрического полиморфизма нету, а есть подстановка и кодогенерация, так что было бы сложно сделать как-то иначе. В дотнете же, если Cat не struct придется бессмысленно тормозить на виртуальных методах, даже если все элементы одного типа на самом деле — из за сабтайпинга это установить невозможно.
V>Это даже в дотнете не так, насколько я знаю.
Дотнет устраняет этот ненужный оверхед для struct. Но типичный класс в дотнете — реф.тип.
K>>И для такой оптимизации никаких трассирующих JIT-ов и прочих ухищрений для оптимизации динамики не нужно. Классы типов проще оптимизировать.
V>Это еще на этапе компиляции делается, например, в случае: V>
V>var pet = new Cat();
V>pet.Kill();
V>
V>Метод Kill будет скомпилирован как обычный вызов, а не виртуальный, в отличие от: V>
V>var pet = petFactory.GetSomePet();
V>pet.Kill();
V>
Мы сейчас про дженерики и констрейнты-интерфейсы. "Мономорфный" код к обсуждению отношения не имеет — там, конечно, проблем с оптимизацией меньше (ровно по той же причине, почему с темплейтами выше все работает — ведь дело нужно иметь только с мономорфным кодом, шаблонный не типизируется и не компилируется).
V>Ну так есть GoF паттерн "стратегия" и даже "визитор", для разных сценариев обсуждаемого. Да, стратегия или визитор подаются "извне".
Ну так это по сути и есть обсуждаемая техника.
V>А как же в случае, когда надо было выразить генератор последовательности Pet a?
Тогда бы хаскелист просто делал последовательность "санков" kill Cat/ kill Dog. Вместо класса со многими функциями — словарь бы завел. Многие хаскелисты и сейчас скорее так сделают, чем экзистенциальные типы используют.
Естественно, такое решение хуже в смысле потребления памяти (для экзистенциального типа достаточно иметь один словарь в памяти на каждый инстанс, при ручной передаче такое разделение словаря между значениями нужно будет специально организовывать) и удобства вроде синтаксиса и когерентности (при ручной передаче программисту самому следить за этим придется), тем не менее экзистенциальные типы многие почему-то не любят.
K>>Никакого противоречия тут нет. Параметрический тип с неподставленными в параметры типами на этапе компиляции вполне конечный и полный. V>Но не является уточненным, всё-равно.
Ну да, так в этом и смысл параметрического полиморфизма.
V>Если в рантайм, то почему не диспетчеризация?
По моему, диспетчеризация подразумевает выбор между какими-то ветками вычислений.
Т.е. если у нас сумма — диспетчеризация есть
foo (Just a) = первый_вариант
foo Nothing = второй_вариант
А Int — не сумма.
foo (I# a) = без_вариантов
Где тут диспетчеризация? Это просто dereferencing.
V>О решении популярного примера на проверку на этапе компиляции длин двух последовательностей на Хаскеле, например.
А, так их там не по десятку, а сколько угодно. Ну и они "сотрутся".
V>Ммм... Не думаю, что для Complex это актуально, даже если у нас Complex<T>.
Для Complex это, конечно, не актуально, но программирование на Complex не заканчивается.
V>Ну тогда не будет CLR библиотек, а только текстовые include их исходников, либо распаршенных вариантов, как в Хаскель. ))
CLR библиотек с такими сигнатурами (с навороченным тайплевелом) да, не будет.
K>>Чего же тут непонятного. Решение с параметрическим полиморфизмом и передачей словарей с раздельной компиляцией совместимо, а "подстановочное" плюсошаблонное — нет.
V>Почему??? Реализация словаря вполне может быть частью библиотеки.
Ну да, можно и на плюсах писать со стиранием, ссылками и передачей словарей, но обычно не пишут. Но это не "подстановочное" решение для которого библиотека нужна будет в исходниках.
V>Видел. В MSVC++ мучались аж до 2005-го года. )) V>Потом взлетело.
Подозреваю, что на сабж потратят гораздо меньше человекочасов, чем плюсам достались, так что это не тот ориентир.
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
Здравствуйте, AlexRK, Вы писали:
ARK>Почему? ARK>Не придираюсь, просто хочу понять. ARK>Есть "готовые блоки" func1 и func2. Я их скомбинировал и получил третью функцию.
Есть калькулятор, у которого только цифровые клавиши, 2 + 2 = набрать нельзя. Но клавиши для операций над числами не нужны: можно же посчитать в уме и набрать сразу 4 (и многие другие числа).
'You may call it "nonsense" if you like, but I'VE heard nonsense, compared with which that would be as sensible as a dictionary!' (c) Lewis Carroll
I>>О, круто, в Swift появилась поддержка шаблонов С++. Любопытная идейка.
V>Да, генерики Swift по-устройству аналогичны шаблонам С++, а не генерикам дотнета. Поэтому вполне корректно.
Ты не волнуйся, я смотрю на твои заявления черз призму "просто я тянул время", "любой студент за два часа", "ios давно не трогал" и тд
Здравствуйте, vdimas, Вы писали:
K>>Нет, связи избегать не нужно. Нужно, по возможности, избегать того, что некий тип может быть представлен как набор операций над ним. Это приводит к потере информации о типе, всяким опасным кастам и т.д.
V>Ага, ну так бы сразу и сказал, что нужно по-возможности избегать парадигмы ООП. ))) V>Я бы даже не пытался возражать в эту сторону. )))
Тип в ООП это АТД + представление + реализация. Если тип это класс то к указаному добавляется еще и модуль где хранится реализация.
Отсюда ясно, что нет никакого избегания ООП.
Соответсвенно, представляя тип как набор операций теряем практически всю информацию. Компилятор не сможет помочь, поскольку только в рантайме будет известно чего же вызовется на самом деле.
Здравствуйте, AlexRK, Вы писали:
ARK>Здравствуйте, andyag, Вы писали:
A>>На дворе 2014ый год, а появляется новый "альтернативно одарённый" язык, которому глубоко плевать на этот самый 2014ый год.
ARK>А что из 2014 года не хватает в Swift?
Как я уже раз 5 здесь написал, хороший яркий пример — рефлексия.
Здравствуйте, NeoCode, Вы писали:
NC>Здравствуйте, andyag, Вы писали:
A>>У вас любимая кошка сходила в туалет по-большому прямо в центре кухни. Предлагаются варианты:
A>>1. Организовать на кухне музей альтернативного искусства A>>2. Убрать A>>3. Посыпать сахаром и оставить на потом
A>>Ваши действия?
NC>Указать оппоненту на то, что сравнение нового языка программирования с продуктом жизнедеятельности кошки как минимум нужно еще обосновать, и весьма серьезно обосновать.
Предложите вашу метафору, с радостью испаганю аналогичным образом.
Здравствуйте, AlexRK, Вы писали:
ARK>Почему? ARK>Не придираюсь, просто хочу понять. ARK>Есть "готовые блоки" func1 и func2. Я их скомбинировал и получил третью функцию.
Да нормальная это комбинация. Просто речь об отсутствие синтаксического сахара соответствующего. Что кстати тоже может быть принципиально в некоторых случаях, т.к. от этого будет зависеть частота применения данной техники. Тем более, что обычно в таких случаях результат комбинации тоже отправляют в какую-то функцию. Т.е. вот допустим у нас есть функции float f1(int) и string f2(float). И мы хотим применить их последовательно к каждому элементу некого массива. Понятно что в этом случае вариант вида:
Однако вариант выше совсем не единственный. В большинстве современных императивных языков можно записать что-то вроде:
map(x=>f2(f1(x)),...)
Ну а если язык хорошо поддерживает шаблоны и перегрузку операторов (типа C++ или D), то можно вообще перегрузить какой-нибудь подходящий оператор и получить код типа такого:
map(f1|f2)
что уже в общем то ничем не отличается от варианта, где у нас есть "комбинирование комбинаторов". Естественно это не единственный нюанс, который требуется для таких дел. Для полного удобства надо добавить в язык ещё несколько подобных вспомогательных штуковин. Но делается абсолютно без проблем, аналогично примеру выше, и более того, уже давно сделано в соответствующих библиотеках.
Здравствуйте, vdimas, Вы писали:
V>Да, генерики Swift по-устройству аналогичны шаблонам С++, а не генерикам дотнета. Поэтому вполне корректно.
А это точно так? Т.е. для меня то это звучит очень позитивно (хотя я не планирую работать на Swift'e, просто из общих соображений). Потому как даже если в шаблон нельзя передавать int'ы и т.п. (т.е. факториал не посчитаешь ), то всё равно будет очень большое пространство для разных сложных и полезны штук из области МП. Но я что-то не заметил в документации каких-то описаний деталей реализации этого дела...
Но зато я заметил вместо этого другое любопытное явление. Как у них сделана параметризация интерфейсов. Не как классов, а с помощью некого typealias. И соответственно реализовать такой интерфейс можно как с помощью обычного класса (тогда надо определить typealias в нём), так и с помощью generic'а (и там Swift автоматом подхватит параметр в качестве typealias). Весьма забавная техника.
Здравствуйте, Klapaucius, Вы писали:
V>>В дотнете иногда используется разметка типов через поддержку интерфейсов-атрибутов с нулевым кол-вом методов в нем, вместо собсно дотнетных атрибутов. Банально быстрее в рантайм. K>А смысл? Словари никто в рантайме не ищет, только в компайлтайм.
Для случая ООП и рантайм-вариант прозрачно для программиста работает.
V>>А да. В плюсах более менее полноценно поддерживается через template-template, в Swift пока ограничено — можно наследоваться от параметра генерика, т.е. в некоторых сценариях выкрутиться можно. На генериках дотнета принципиально нельзя, ес-но, ведь для такого трюка нужна та самая "текстовая развертка" как ты её обозвал.
K>Нет, не нужна. В хаскеле, скале и других для этого текстовая развертка не требуется. Ее, в принципе, даже в шарп можно добавить без изменения CLR при отказе от возможности обобщать конструкторы value-типов, что, прямо скажем, очень экзотический юзкейс.
Все-равно не представляю, как ср-вами CLR хранить описание таких типов.
И если не сложно, раскрой, плиз выделенное.
V>>3. не приемлю AST-синтаксис арифметических выражений: d.mul(d.add(var1, var2), var3). K>Это безоговорочно плохо, но подавляющее большинство имен в дотнетных либах — альфанумерик и эта проблема касается мало чего кроме арифметики.
В среднем коде наблюдается этой арифметики, в т.ч. булевой логики — до половины. Весь остальной мильон альфанумериков делят оставшиеся %% кода м/у собой. Отсюда такое внимание именно операторам языка — они ведь составляют приличную долю синтаксиса исходника.
K>Просто подтипирование это действительно плохо. Что проявляется на многих уровнях, от стимулирования написания полудинамического глюкокода до проблем со взрывающими мозг ко/контравариантностями (и соотвествующими багами, вроде дыры в дотнет-массивах) и выводом/реконструкцией типов.
Ко/контрвариантность — тоже инструмент упорядочивания кода при глубине иерархий больше 2. В плюсах раньше всего это ввели в сигнатуры виртуальных методов — это моментально помогло решить проблему с избыточным дауншифтингом при реализации интерфейсов на сложных иерархиях. Т.е. элегантно и типобезопасно побороли самую страшную ООП-граблю.
K>Фан факт: чтоб сделать хаскель полностью непригодным для функционального программирования — в него достаточно сабтайпинг добавить и все, больше ничего делать не нужно.
Тогда это будет гибрид, как C# или C++ с чуть более продвинутой системой типов. ))
В любом случае, нечто вроде сабтайпинга накрутить можно и в Хаскель:
type OutputStream a = StreamBase (COutputStream a)
Ведь контракты ООП — это тоже ограничения. ))
В классике системного подхода любое требование автоматически является ограничением.
В итоге, иерархия ограничений Хаскеля в точности равна иерархии интерфейсов ООП с т.з. дизайна.
K>В том и дело. Разница между "можно заинлайнить в 99% случаев" и "нельзя заинлайнить в 99% случаев" одной "игрой терминов" не достигается.
Ну это в дотнете, разве что, а не плюсах (или Swift). Ты не обратил ниже внимание на разницу Cat и Cat*. Просто для дотнета этой разницы нет, ведь в нем всегда Cat* для классов.
V>>Ага, ну так бы сразу и сказал, что нужно по-возможности избегать парадигмы ООП. ))) V>>Я бы даже не пытался возражать в эту сторону. )))
K>Под ООП люди что угодно могут подразумевать, так что я выразился конкретнее.
Ну да, св-ва гибридных языков начинают приписывать ООП в целом. Но ты выразился именно про парадигму ООП. Спорить не хочу, т.к. полно сценариев, где ООП-парадигма работает прекрасно. Например, сокет или окошко GUI, кароч везде, где конечный объект "непрозрачен" по объективным причинам. Даже в Хаскеле в сетевых и GUI либах сплошное ООП.
V>>Если бы словарь не надо было организовывать в виде вспомогательного (по-сути, фиктивного) типа, то был бы эквивалент. Если в С++ введут полноценные ограничения на аргументы шаблонов, то эквивалент будет возможно реализовать на С++.
K>Так ведь словарь и в хаскеле реализован с помощью "вспомогательного фиктивного типа"
Класс типов это не тип, а контракт. Его "воплощения" в природе не существует.
V>>У нас на С++ только целевые классы обычно не обобщенные, но сами они практически целиком и полностю построены как комбинация обобщенных кубиков низлежащего уровня. Что мы делаем не так?
K>Все же уровень не сравнимый. В плюсах обобщение явное и синтаксически тяжеловесное. В ФЯ обобщение по умолчанию.
Это лишь в языках-наследниках ML. Обобщение и ФП — вещи глубоко ортогональные друг другу, так же как обобщение в ООП. Полно функциональных языков безо-всякого обобщения. Как альтернатива — в таких языках обычно есть макросы.
K>>>Нет, так не делается, потому что разделения на тип и словарь нет. V>>Есть, конечно. K>Ну да, делается в том числе и так, но это более позднее изобретение. Первоначальный ООП-подход предполагает, что словарь и данные — вместе.
Ну да. Но мы же сравниваем конкретные языки. А они гибридные. ))
V>>В чем суть этих проблем? K>В отсутствии раздельной компиляции.
Сам словарь-то для конкретного типа зачастую не обобщенный нифига, какие проблемы с раздельной компиляцией?
K>Как в С++ можно когерентность инстансов организовать?
Если я правильно понял вопрос, то через области видимости. Через неймспейсы, например.
Если не в тему, то расшифруй вопрос, плиз.
V>>В этом смыслу классы типов ну ОЧЕНЬ похожи на интерфейсы ООП.
K>У вас, по-моему, далекое от реальности представление о классах типов. K>Да, без всякой оптимизации класс типов это та же структура с ссылками на функции. Но разница в том, что для класса типов мы обычно уже в компайл тайм знаем, что это за функции.
Можно мне, тупому, показать на пальцах пример в Хаскель, когда бы у нас была последовательность абстрактных Pet a, и к ней некоторая библиотека, которая вызывает kill pet, проходя по этой последовательности. Причем, конкретная конечная сумма {Dog, Cat, Parrot, ...} этой библиотеке заранее не известна (на то она и библиотека). И еще, чтобы это была раздельная компиляция. Вот сценарий: из файла мы читаем список РАЗНЫХ животных и обрабатываем их последовательность библиотечной ф-ией, а не делаем предварительный ПМ на клиентской, по отношению к либе, стороне.
K>Именно потому инлайн и возможен (хотя для инлайна еще требуется доступность того, что инлайнится).
Вот давай посмотрим на инлайн в этой задаче.
K>В случае же ООП мы наоборот как правило не знаем в компайл-тайм на какие функции там ссылки, доступны эти функции для инлайна или нет — уже не важно, нам не известно что инлайнить.
Это зависит от того, что "видит" компилятор — сам объект или только его адрес. Если компилятор С++ видит Cat по-значению (на стеке, глобальную переменную или как поле другого объекта), то он вызывает методы напрямую, если же он видит Cat* или Cat&, то он понятия не имеет (в общем случае) о точном типе объекта по адресу, поэтому работает рантайм-полиморфизм.
V>>В случае Хаскеля мы имеем "автосуммы" допустимых типов выражений прямо в точке вызова некоей ф-ии, описанной в классе типов. Компилятор лепит там рантайм-ПМ прозрачно для программиста.
K>Нет, никакой автосуммы типов и рантайм-ПМ по ней в точке вызова функции-члена класса нет. Компилятор подставляет вместо члена класса код специализированной функции в лучшем случае и ссылку на специализированную функцию в худшем.
В последнем неточность. Если в бинарнике вшит адрес ф-ии (прямая адресация) то диспетчеризации нет. Если же там переменная/ячйка, в которой хранится адрес (а в ФП так почти всегда), то это косвенная адресация, она же диспетчеризация. Последнее — это полный аналог интерфейса с одним методом, то бишь тот самый рантайм-полиморфизм.
В любом месте виртуального вызова метода в ООП мы имеем дело с ОДНИМ только методом, т.е. всего с одной интересующей ячейкой для косвенного вызова, т.е. глубоко пофиг, один метод содержится в интерфейсе или несколько. Целиком таблица виртуальных вызовов не нужна ни разу и никем не просматривается — сразу берется нужный адрес в известной, на этапе компиляции, её строке.
V>>Известно только если компилятор не видит точный (конечный) тип объекта. Если же видит, то вызывает виртуальные методы напрямую, без таблицы. Именно поэтому я вижу тут аналогию.
K>Но компилятор обычно не видит конечный тип объекта. И из-за сабтайпинга в том числе, кстати.
Это в джаве-дотнете, бо эти языки работают не с самими объектами, а с указателями на них, то бишь ВСЕГДА речь идет о косвенных вызовах, всегда речь идет о диспетчеризации. Там же, где доступна прямая адресация, там известен точный тип. Например, value-type в дотнете.
K>В ООП сложно обозначить область где рантайм-полиморфизм не требуется.
Чем больше возможности обобщенного программирования, тем меньше надо разводить иерархий и паттернов GoF на ровном месте. Цель-то всех этих техник банальна — повторное использование кода. Полиморфизм — лишь одна из техник. По отношению к парадигме ООП, наследование реализаций — это просто некий трюк где-то сбоку. Можно взять для рассмотрения более строгую парадигму КОП — наследование только интерфейсов.
Ну и в обычных пользовательских классах доля виртуальных методов далеко не 100%, а как бы заметно ниже 50%.
K>Крайне сложно выявить ее статическим анализом.
Блин...
Если компилятор "видит" как сформирована ячейка памяти, то он знает точный её тип.
Например, средний граф объектов в памяти при решении одной и той же задачи на С++ и C# резко отличается. В C# будет взрывающий мозг граф из-за того, что 99.99% объектов будут ссылочные. А в С++ используют владение по ссылке в самом крайнем случае — только если сама задача диктует такой дизайн, когда конечный тип объекта заведомо не может быть известен. То-бишь, такой рантайм-полиморфизм является целевым и был бы таким же "рантайм-чего-то-там" на любом языке. В остальных случаях в С++, при разработке типов, обычно располагают поля по-значению (исключу пока подход p-impl, он специфичен, обычно только для фасадных/публичных классов проприетарных библиотек).
В итоге, в методах объектов верхних уровней, при обращении к методам собственных объектов-полей, реализуется статический вызов методов. Причем, речь всё еще идет не о гибридных возможностях языка, а о самых что ни на есть ООП-фичах.
В общем, с заявлением "В ООП сложно обозначить область где рантайм-полиморфизм не требуется" согласиться оч сложно. По-сути, ты приписал всему ООП ограничения пары популярных платформ — дотнета/джавы.
Более того, нет никаких ограничений, даже в условиях GC, располагать объекты по-значению в теле объектов-владельцев. В современном дотнете есть управляемые ссылки на "где-то в середине объекта", которые во время исполнения представлены точно таким же обыкновенным адресом, как и рутовая управляемая ссылка на объект и точно так же обрабатывается GC. Поэтому эта техника вполне могла бы быть реализована уже сейчас.
K>Виртуальные вызовы только какой-нибудь трассирующий джит нормально устранить сможет.
Намного больше их устранит нивелирование избыточной/ненужной косвенности.
K>Поэтому, там где рантайм полиморфизм требуется — там он везде будет. А там, где не требуется — в ООП скорее всего будет, а в хаскеле — нет.
В дотнете/джаве скорее будет. В других ООП-языках — скорее нет (C++/D/ObjectPascal/Swift и т.д.)
Косвенность и ООП — тоже ведь вещи ортогональные.
V>>а в случае vector<Cat> — этапа компиляции. K>Что достигается тем, что параметрического полиморфизма нету, а есть подстановка и кодогенерация, так что было бы сложно сделать как-то иначе.
Странно, что ты не понял еще в прошлый раз, о чем речь. Пофиг на шаблонный vector<>, пусть у нас будет два обычных массива:
Cat cats;
Cat* catsByPtr[];
При обработке первого массива методы объектов будут вызываться напрямую (со всей оптимизацией и инлайнингом, на которую способен компилятор), при обработке второго массива — как виртуальные.
K>В дотнете же, если Cat не struct придется бессмысленно тормозить на виртуальных методах, даже если все элементы одного типа на самом деле — из за сабтайпинга это установить невозможно.
Это не из-за сабтайпинга, а из-за того, что только при косвенной адресации сабтайпинг начинает нам мешать. Никто не мешал в дотнете придумать, например, наследование структур. Это вообще никак не запрещено и ничего бы не поломало. Про стирание грани м/у ref- и value-type я уже высказывался.
V>>Это даже в дотнете не так, насколько я знаю. K>Дотнет устраняет этот ненужный оверхед для struct. Но типичный класс в дотнете — реф.тип.
Во-во. Сам себе ответил.
Это всё унаследованный от джавы кретинизм. Парадигма ООП тут не при чем.
K>Мы сейчас про дженерики и констрейнты-интерфейсы. "Мономорфный" код к обсуждению отношения не имеет — там, конечно, проблем с оптимизацией меньше (ровно по той же причине, почему с темплейтами выше все работает — ведь дело нужно иметь только с мономорфным кодом, шаблонный не типизируется и не компилируется).
V>>Ну так есть GoF паттерн "стратегия" и даже "визитор", для разных сценариев обсуждаемого. Да, стратегия или визитор подаются "извне". K>Ну так это по сути и есть обсуждаемая техника.
В С++ более распространены их статические варианты: traits и "статический визитор". А так-то да. Если забыть, что речь шла про операторы... ))
С операторами удобнее таки через явное указание "модуля", то бишь области видимости. Именно таким образом мы "передаем словарь" один раз на всю текущую область видимости (через указание других видимых явно "модулей"), т.е. не надо передавать в каждый метод. Очень даже оправданный подход, коль речь не о вызовах методов, а о глобальных ф-иях/операторах.
V>>А как же в случае, когда надо было выразить генератор последовательности Pet a? K>Тогда бы хаскелист просто делал последовательность "санков" kill Cat/ kill Dog.
Меня интересует библиотечная либа, умеющая вызывать kill Pet a, которая еще ничего не знает о Cat и Dog, и которая принимает на входе выраженную каким-либо способом последовательность абстрактных Pet a.
K>Вместо класса со многими функциями — словарь бы завел. Многие хаскелисты и сейчас скорее так сделают, чем экзистенциальные типы используют.
Я бы посмотрел на оба варианта. Если можно.
K>Естественно, такое решение хуже в смысле потребления памяти (для экзистенциального типа достаточно иметь один словарь в памяти на каждый инстанс, при ручной передаче такое разделение словаря между значениями нужно будет специально организовывать) и удобства вроде синтаксиса и когерентности (при ручной передаче программисту самому следить за этим придется), тем не менее экзистенциальные типы многие почему-то не любят.
Есть соображения насчет последнего?
V>>Если в рантайм, то почему не диспетчеризация? K>По моему, диспетчеризация подразумевает выбор между какими-то ветками вычислений.
Самый популярный способ диспетчеризации на сегодня — это косвенная адресация. Есть код, который вызывает другой код не по известному на момент компиляции адресу, а по фактическому поданному в процессе работы программы. Собсно, большинство паттернов GoF на этом построены, кроме совсем уж экзотических, типа "интерпретатора".
K>Где тут диспетчеризация? Это просто dereferencing.
)))
Вызов виртуального метода и есть "просто dereferencing".
K>Для Complex это, конечно, не актуально, но программирование на Complex не заканчивается.
Ну... в С++ есть template-template, а в дотнете можно рожать в рантайм конечные типы из генериков через рефлексию. Согласен, что можно было бы добавить в синтаксис.
V>>Почему??? Реализация словаря вполне может быть частью библиотеки.
K>Ну да, можно и на плюсах писать со стиранием, ссылками и передачей словарей, но обычно не пишут. Но это не "подстановочное" решение для которого библиотека нужна будет в исходниках.
В плюсах в исходниках нужны только заголовочные файлы для этого случая, а тело методов может быть библиотечным. Если мы всё еще об одном и том же — о реализации словаря ComplexNumber, например. ну и инлайнинг на сегодня может выполняться во время линковки, не только во время компиляции.
V>>Видел. В MSVC++ мучались аж до 2005-го года. )) V>>Потом взлетело.
K>Подозреваю, что на сабж потратят гораздо меньше человекочасов, чем плюсам достались, так что это не тот ориентир.
— Сабж изначально проще в синтаксисе, сам синтаксис имеет более однозначную семантику, в отличие от жутко неоднозначной семантики выражений С++ (которая зависит от увиденных "где-то раньше" чего угодно, например using blah-blah).
— Сабжу не надо воровать львиную долю человекочасов на собственный бэкенд и оптимизацию конечного бинарного кода. Особенно это актуально для современного числодробления.
— LLVM последних версий отстаёт от самого лучшего GC уже всего лишь в полтора раза на специальных тестах и почти не отстаёт на "обычном" коде.
В общем, потенциал неплох. По-сути, это прямой конкурент D. Причем, за этим языком стоит IT-компания из первой тройки. Неплохо.... это тебе не Sun+Java когда-то. А ведь "взлетело" в итоге и это сановское г-но на палочке... )))
Здравствуйте, Ikemefula, Вы писали:
V>>Да, генерики Swift по-устройству аналогичны шаблонам С++, а не генерикам дотнета. Поэтому вполне корректно. I>Ты не волнуйся, я смотрю на твои заявления черз призму "просто я тянул время", "любой студент за два часа", "ios давно не трогал" и тд
Тебе вообще тут нечего делать. Только лишний шумовой фон.
Здравствуйте, vdimas, Вы писали:
K>>Для автоматической подстановки словаря достаточно поддержки компилятора, чтоб он в области видимости искал структуру с имплементацией нужного интерфейса и, допустим, атрибутом TypeClass.
V>В дотнете иногда используется разметка типов через поддержку интерфейсов-атрибутов с нулевым кол-вом методов в нем, вместо собсно дотнетных атрибутов. Банально быстрее в рантайм.
Чудеса да и только. Маркер-интерфейс требует примерно таких же расходов в рантайме, как и поиск по атрибутам. Затраты на разработку меньше с аттрибутами. Итого — где тут "быстрее", не ясно.
Если речь только про типы, которые доступны компилятору, то самый быстрый способ — компайл-тайм интроспекция. Все остальное, типа маркер-интерфейсы или атрибуты, нужно в том случае, кода типы недоступны компилятору. Например, они будут известны только после деплоймента — плагины-аддоны всех сортов, новые версии фремворка и тд и тд
Здравствуйте, Klapaucius, Вы писали:
K>Здравствуйте, AlexRK, Вы писали:
ARK>>Почему? ARK>>Не придираюсь, просто хочу понять. ARK>>Есть "готовые блоки" func1 и func2. Я их скомбинировал и получил третью функцию.
K>Есть калькулятор, у которого только цифровые клавиши, 2 + 2 = набрать нельзя. Но клавиши для операций над числами не нужны: можно же посчитать в уме и набрать сразу 4 (и многие другие числа).
А без аналогий можете пояснить, что в моем случае не так?
Здравствуйте, alex_public, Вы писали:
_>что уже в общем то ничем не отличается от варианта, где у нас есть "комбинирование комбинаторов". Естественно это не единственный нюанс, который требуется для таких дел. Для полного удобства надо добавить в язык ещё несколько подобных вспомогательных штуковин. Но делается абсолютно без проблем, аналогично примеру выше, и более того, уже давно сделано в соответствующих библиотеках.
Понял. То есть речь просто о сахаре для вызовов цепочек функций. Тогда зачем же притягивать целый ФЯ?
Здравствуйте, Klapaucius, Вы писали:
K>В том и дело, что дорогие/сложные фичи из функциональщины вроде лямбд, дженериков и иммутабельных строк вполне в мейнстрим пролезли. Отсюда и вопрос: что такого с ПМ, что он не пролезает? Я на него ответа не знаю, а вы?
С того, что эффективный и безопасный ПМ возможен только на закрытых суммах типов, а не на открытых, которые представляют из себя иерархии классов в ООП.
Еще непонятно как эффективно разместить размеченное объединение в памяти, т.е. как эффективно всем этим пользоваться при минимуме накладных расходов. Например, динамическое приведение в ПМ в Немерле показывает всю кривизну такой попытки.
K>Ну вот, вы же сами написали, что все, кроме C и C++. Остальные это ведь не только пара-тройка ФЯ но и бразиллион языков никакого отношения к ФП в основном не имеющих. Как видите, страшный синтаксис им никак не помог. При этом сабж явно не окажется в категории С/C++, а наоборот — в категории "остальные", в которой ФЯ часто смотрятся в смысле производительности очень хорошо.
Наверно, стоит сравнивать примерно равные по популярности языки.
I>>Сишный синтаксис очень хорош для структур данных, ООП и подобных вещей. Скажем, для описания данных лучше чем JSON не придумаешь.
K>Довольно странное утвеждение. Описание структур данных в С-подобных языках как раз часто очень многословное и навороченное. K>Вообще, я считаю, что где нет сумм — там и нормальных возможностей описания структур данных нет.
+1
любые IDL/MIDL и т.д. умеют описывать размеченные объединения.
I>>Убогий паттерн матчинг на порядок лучше его отсутствия. K>Вопрос был в том, зачем делать убогим, если можно не убогим? Что мешает-то?
Отсутствие размеченных объединений в языке.
K>Нет, я не про убогие возможности, а именно про навороченность синтаксиса.
Дык, если в Хаскель всего три типа данных — это туплы, размеченные объединения и сигнатуры ф-ий, то синтаксис будет минимальным, конечно. Тем более, что в условиях иммутабельности нет надобности скрывать устройство типов, т.е. отпадают еще всякие public/protected/private.
А если бы были еще отдельно структуры, отдельно классы (не классы типов, а классы ООП), интерфейсы и т.д. и т.п. + разметка прав доступа к членам составных типов — то вот тебе сразу куча синтаксиса.
Избыток синтаксиса в ООП только в оформлении деклараций типов. Тела ф-ий в С++-синтаксисе смотрится нормально, особенно с переопределением операторов.
Здравствуйте, Ikemefula, Вы писали:
K>>Нет, вывести такое следствие не из чего. Вот если бы были языки с равными инфраструктурами и сильно различающиеся по фичам — тогда можно было бы какие-то выводы делать.
I>Хорошая инфраструктура почему то появляется только для самых убогих языков с твоей точки зреня. Тебе самому не смешно ?
Так и есть. Для одного из самых убогих языков — Джавы, серьезная инфраструктура появилась одной из первых.
Здравствуйте, vdimas, Вы писали:
V>Здравствуйте, Ikemefula, Вы писали:
K>>>Нет, вывести такое следствие не из чего. Вот если бы были языки с равными инфраструктурами и сильно различающиеся по фичам — тогда можно было бы какие-то выводы делать.
I>>Хорошая инфраструктура почему то появляется только для самых убогих языков с твоей точки зреня. Тебе самому не смешно ?
V>Так и есть. Для одного из самых убогих языков — Джавы, серьезная инфраструктура появилась одной из первых.
"одной из первых" — смеялся. Эдак окажется, что до джавы жизни вообще не было.
Здравствуйте, AlexRK, Вы писали:
_>>что уже в общем то ничем не отличается от варианта, где у нас есть "комбинирование комбинаторов". Естественно это не единственный нюанс, который требуется для таких дел. Для полного удобства надо добавить в язык ещё несколько подобных вспомогательных штуковин. Но делается абсолютно без проблем, аналогично примеру выше, и более того, уже давно сделано в соответствующих библиотеках.
ARK>Понял. То есть речь просто о сахаре для вызовов цепочек функций. Тогда зачем же притягивать целый ФЯ?
А как ты думаешь это всё работает унутре ? Этот 'просто сахар' внятной поддержки компилятора, рантайма и фремворка. В частности, нужны толковые структуры данных. Кроме того обязательно требуются замыкания. Замыкания для полноценной реализации требуют GC. Опаньки !
Здравствуйте, Ikemefula, Вы писали:
ARK>>Понял. То есть речь просто о сахаре для вызовов цепочек функций. Тогда зачем же притягивать целый ФЯ?
I>А как ты думаешь это всё работает унутре ? Этот 'просто сахар' внятной поддержки компилятора, рантайма и фремворка. В частности, нужны толковые структуры данных. Кроме того обязательно требуются замыкания. Замыкания для полноценной реализации требуют GC. Опаньки !
Ну, GC и замыкания — это не целый ФЯ. Кстати, а нафига тут вообще замыкания?
Здравствуйте, AlexRK, Вы писали:
I>>А как ты думаешь это всё работает унутре ? Этот 'просто сахар' внятной поддержки компилятора, рантайма и фремворка. В частности, нужны толковые структуры данных. Кроме того обязательно требуются замыкания. Замыкания для полноценной реализации требуют GC. Опаньки !
ARK> Ну, GC и замыкания — это не целый ФЯ. Кстати, а нафига тут вообще замыкания?
GC и замыкания это 90% от ФЯ Замыкания нужны для комбинирования функций. компилер по сахару будет генерить эти самые замыкания, т.е. фактически, именно через них все и будет работать.
Не всегда конечно, в стандартных случаях можно обойтись предопределенными функциями.