Здравствуйте, Klapaucius, Вы писали:
EP>>На C++ есть всякие DSL библиотеки, позволяющие писать высокоуровневый, который отлично мэппятся в железо. Например Eigen может во время компиляции выбрать оптимальный путь вычисления матричного выражения, и автоматически векторизовать. K>Это пример куда лучше, но, тем не менее, числодробление не особенно высокоуровнево.
Над этим числодроблением можно точно также надстроить абстракции (бесплатные/дешёвые), более близкие к конкретной предметной области. Например Eigen+МКЭ.
EP>>Опять-таки высокоуровневый != огромное penalty. K>Это, конечно, верно. Однако подход высокоуровневого языка заключается в том, чтоб дать широкий набор инструментов, в том числе и таких, где penalty неизбежно, однако постараться минимизировать это "огромное penalty". Ну а подход "вот тут мы можем как-то обойтись без огромного penalty, значит это у нас будет — вот это — нет." — это подход низкоуровневого языка.
В C++ по-другому. Там не "то что дорого — запрещено", а "you don't pay for what you don't use".
За то что нужно платить — выключено по умолчанию, но это не означает что оно полностью запрещено.
EP>>В большинстве кода не то что shared_ptr нет, а даже unique_ptr. K>В большинстве низкоуровневого кода — да, в большинстве идиоматического ФП-кода — нет.
Например C#/Java — это какие по-вашему? Низкоуровневые?
EP>>Где вычисляется то что ненужно? K>В примере "монадизации" на D, например.
Это достаточно искусственный/специфичный пример. Там точно также как и в foldl происходят лишние вычисления.
EP>>Выводы на основе ложных утверждений. Далеко не все абстракции дорогие. K>Но многие абстракции, востребованные для написания ФП кода как раз дорогие.
А для чего востребован сам этот ФП код? ("этот" — о котором идёт речь, с дорогими абстракциями. (отдельные ФП элементы действительно имеют полезные свойства, необходимые при решении практических задач, и при этом бесплатны/дёшевы)).
K>>>Поэтому никакого высокоуровневого кода и нет — есть только продвинутый интерфейс для двигания указателей. А поддержка высокоуровневых инструментов ограничивается заявлениями о их ненужности. EP>>См. Eigen. K>Смотрим. То, что не использовано для написания Eigen — не нужно?
Нужно, но не везде и не всегда.
То есть весь вопрос в том, насколько часто у дорогих абстракций нет дешёвых альтернатив.
EP>>1. Какую abstraction penalty вносит указатель сам по себе? K>Указатель — это абстракция "плоская память произвольного доступа",
Память плоская только в пределах массива/структуры.
K>если мы не "проделаем в ней дыру" и не станем учитывать иерархию памяти и прочее, от чего она нас вроде бы должна абстрагировать — мы будем биться об стену памяти, например. Вот такая вот penalty.
Сам указатель не даёт никакого penalty. Он не отражает иерархичность, но не исключает её.
Если нужен быстрый код — нужно так или иначе учитывать GCD особенности целевых систем. Например для учёта иерархической организации памяти разрабатываются сache-oblivious алгоритмы.
Что и как можно встроить в понятие указатель для отражения иерархичности? Что это даст по сравнению с тем что есть сейчас?
(особенно нужно учесть, что C++ код работает на разном железе, включая восьми битные микроконтроллеры).
Re[64]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
I>>Это вобщем другими словами примерно следующее "высокоуровневый код писать легко". Но я так понял, ты и с этим будешь спорить ?
_>А причём тут это? Ты писал про какие-то различия уровня типизации обобщённого кода на C++ и Haskell'е. Так вот, если мы говорим про некий C++ проект с шаблонами, компилируемый в исполняемый файл (а не шаблонную библиотеку, поставляемую в виде заголовочных файлов), то разницы с Хаскелем нет никакой, в принципе. А именно, компилятор и там и там чётко сообщит о всех некорректностях и просто не скомпилирует проект в случае наличия каких-то ошибок.
Мы говорим про библиотеку а не готовый исполняемый продукт.
Если ввели эти и другие мульки, то с хаскелем разница только в раздельной компиляции.
I>>То есть, ты утверждаешь, что компилятор сумеет отличить правильную с т.з. реализацию математики реализацию bind и неправильную ? I>>Я правильно тебя понял ?
_>Это никакой компилятор не сможет проверить, т.к. для этого надо смотреть не только на прототип функции bind (что компилятор может), но и на её реализацию.
Я про статистический анализатор, а не компилятор.
I>>Шаблоны C++ работают исключительно во время компиляции и при этом они динамически типизированы.
_>Что-то я тебе вообще перестал понимать. Ты вообще о чём говоришь то? )))
In C++, templates are not separately type checked. Instead, type checking is
performed after instantiation at each call site. Type checking of the call site can
only succeed when the input types satisfy the type requirements of the function
template body. Unfortunately, because of this, if a generic algorithm is invoked
with an improper type, complex, long, and potentially misleading error messages
may result.
Re[65]: Есть ли вещи, которые вы прницпиально не понимаете...
1. Указанная вещь без проблем работает ещё в древнем стандарте языка. Просто она в нём записывается с кучей синтаксического мусора (через enable_if), а предлагается сделать отдельную симпатичную запись. Т.е. по сути это практически синтаксический сахар, хотя и весьма сильный.
2. И старая реализация и указанное (вроде как это планируют в C++14), работают исключительно в процессе использования шаблона. Т.е. здесь опять же требуется инстанцирование, для включение контроля компилятором. Но лично я не вижу в этом никаких существенных ограничений.
3. Собственно говоря именно контроль типов в C++ шаблонах полноценно работает и без всяких концептов, просто выдаются слегка разные сообщения об ошибках ("Cont — это не Sortable тип" vs "у Cont не определён оператор >"), что совсем не принципиально. А реальная польза от концептов (сейчас для этого используют страшные выражения с enable_if) в возможности перегрузки шаблонов без указания конкретных типов. Это позволяет например определить общий алгоритм для InputIterator и более оптимальный для RandomIterator и т.п.
I>Я про статистический анализатор, а не компилятор.
Хы, ну я про это ничего не знаю. Не пользуюсь ими вообще. )))
I>
I>In C++, templates are not separately type checked. Instead, type checking is
I>performed after instantiation at each call site. Type checking of the call site can
I>only succeed when the input types satisfy the type requirements of the function
I>template body. Unfortunately, because of this, if a generic algorithm is invoked
I>with an improper type, complex, long, and potentially misleading error messages
I>may result.
Ну да, всё правильно. Я только не пойму к чему ты написал эти очевидные истины. )
Re[65]: Есть ли вещи, которые вы прницпиально не понимаете...
.
I>Если ввели эти и другие мульки, то с хаскелем разница только в раздельной компиляции.
1. Это Concepts Lite. Как уже сказал alex_public, на тело шаблона они не накладывают ограничения (хотя первый вариант концепций C++0x ограничивал и тело шаблона). Тут подробнее.
2. Всё что дают Concepts Lite помимо сахара, по сравнению с тем что есть сейчас — это разбиение требований на атомы, и автоматическая перегрузка по тестам множеств (сейчас нужно играть с тэгами, enable_if/disable_if/etc).
3. У них есть shorthand-notation. Например, варианты кода выше:
Здравствуйте, alex_public, Вы писали:
I>>Если ввели эти и другие мульки, то с хаскелем разница только в раздельной компиляции.
_>1. Указанная вещь без проблем работает ещё в древнем стандарте языка. Просто она в нём записывается с кучей синтаксического мусора (через enable_if), а предлагается сделать отдельную симпатичную запись. Т.е. по сути это практически синтаксический сахар, хотя и весьма сильный.
enable_if это костыль
_>2. И старая реализация и указанное (вроде как это планируют в C++14), работают исключительно в процессе использования шаблона. Т.е. здесь опять же требуется инстанцирование, для включение контроля компилятором. Но лично я не вижу в этом никаких существенных ограничений.
Я куда ни смотрю, то у тебя в С++ всё шоколадно, хотя ты этим не пользуешься, но вот в другом языке только ужос-ужос-ужос.
_>3. Собственно говоря именно контроль типов в C++ шаблонах полноценно работает и без всяких концептов, просто выдаются слегка разные сообщения об ошибках ("Cont — это не Sortable тип" vs "у Cont не определён оператор >"), что совсем не принципиально. А реальная польза от концептов (сейчас для этого используют страшные выражения с enable_if) в возможности перегрузки шаблонов без указания конкретных типов. Это позволяет например определить общий алгоритм для InputIterator и более оптимальный для RandomIterator и т.п.
Вобщем то принципиально, а то многие ошибки с шаблонами по количеству текста превосходят любые самые худшие ожидания
I>>
I>>In C++, templates are not separately type checked. Instead, type checking is
I>>performed after instantiation at each call site. Type checking of the call site can
I>>only succeed when the input types satisfy the type requirements of the function
I>>template body. Unfortunately, because of this, if a generic algorithm is invoked
I>>with an improper type, complex, long, and potentially misleading error messages
I>>may result.
_>Ну да, всё правильно. Я только не пойму к чему ты написал эти очевидные истины. )
Ты же с этим несогласен
Re[40]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>1. Добавление верификатора поломает backward compatability. EP>2. Для разных целей нужны разные средства — кому-то нужен один верификатор, кому-то другой.
Ок, это аргумент.
EP>Ну да — так там её просто нет, но тем не менее это не мешает его использовать.
Я пока скептически отношусь к Node.js. В своё время казалось, что тяжеловесные ОРМ — это ответ на все вопросы. А потом внезапно выяснилось, что они создают больше проблем, чем решают. Вот и Node.js — интересный эксперимент. Году к 2020 посмотрим, что из него имеет смысл вписывать в скрижали, а что окажется смешным заблуждением наивных пропонентов.
EP>Если нет возможности сделать вечный цикл — то это не аналог while.
А для чего вам вечный цикл?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[67]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Ikemefula, Вы писали:
I>enable_if это костыль
Ну так свои задачи то выполняет и не плохо. Не зря же его в итоге в стандарт ввели. )
I>Я куда ни смотрю, то у тебя в С++ всё шоколадно, хотя ты этим не пользуешься, но вот в другом языке только ужос-ужос-ужос.
Не не не, ты забываешь про D — вот там действительно всё шоколадно. ))) Если бы для D сейчас был готовый Boost, wxWidgets и ещё несколько подобных библиотек, плюс мощная IDE (хотя это уже даже не обязательно) уровня Netbeans/Eclipse/VS, то C++ выкинул бы прямо сегодня. ))) Хотя нет, надо бы ещё компилятор D под ARM и микроконтроллеры (такое сейчас даже не намечается, хотя технически возможно), вот тогда точно можно выкидывать.
I>Вобщем то принципиально, а то многие ошибки с шаблонами по количеству текста превосходят любые самые худшие ожидания
Хы, это чуть ли не единственная здравая претензия к C++ шаблонам в этой темке. )))
I>Ты же с этим несогласен
Где это было? )
Re[74]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Ну и кстати говоря оно даже не формальная монада, т.к. там реально у Nullable нет какой-то спец. функции, которая вызывается в этих местах с передачей ей оператора.
Конечно же не монада. Нормальная монада должна была не только операторы залифтить, но и любые функции от базового типа.
А там всё делает сам компилятор по месту, так что это больше всего напоминает просто перегрузку всех операторов для данного типа.
Ага. Просто монадный компилятор мог бы это делать автоматом для любых монад, а не только для встроенных в язык.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[68]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>вне монад.
Ну так что с того?
_>Но можно же было не ставить такие условия, а сделать помягче. Например сделать чистоту не обязательной, а опциональной (как в D). Ленивость кстати при этом можно не убирать полностью — просто она будет работать только для соответствующих мест. Тогда внешний вид программы стал бы как на других функциональных языках, без монадного ужаса.
Ну был бы еще один ФЯ-инвалид вроде SML/OCaml. Ведь любое ослабление контроля за эффектами придется разменять на удобства в коде и оптимизации. И это при том, что тему ужасности "монадных ужасов" раскрыть так и не удалось.
Лучше было бы, если бы контроль за эффектами в хаскеле можно было наоборот усилить, чтоб учитывать частичность/незавершаемость функций, это бы дало еще больше полезной информации и для анализатора строгости и для инлайнера, что позволило бы делать более агрессивные оптимизации и рассуждать о коде без всяких скидок на боттомы.
K>>Непонятно, почему ленивость как скрытая мутабельность — это не то, а превращение рекурсии в цикл, как скрытая мутабельность — то. Первое встречается гораздо чаще второго, и позволяет оптимизировать гораздо более широкий спектр рекурсивного кода.
_>Ну так она же не всегда позволяет оптимизировать.
Не всегда, но для полноценной поддержки рекурсии ленивость полезнее TCO именно за счет более широкого охвата. Хвостовая рекурсия по какой-то причине сильно раскручена во всяких вводных статьях по ФЯ, хотя сама по себе редко встречается (а те случаи, когда она переписывается именно в цикл — и того реже), переписывание в хвосторекурсивной форме часто уродует код (хвосторекурсивная лапша с аккумуляторами не сильно лучше циклов выглядит, ручная CPS-трансформация и того хуже), про всякие развороты списков задом наперед и лишнюю работу на ровном месте я и не говорю.
_>Так можно всё же получить не некие общие рассуждения, а конкретные ответ: _>1. сколько выделений памяти будет в указанном коде? _>2. как будет выглядеть на haskell'e код, реализующий тоже самое, но с нулём выделений памяти (размещение исходных данных не считаем)?
Конкретный ответ получить можно. Вот он:
import qualified Data.Vector.Generic as V
import qualified Data.Vector.Generic.Mutable as M
import Control.Monad
-- это у нас "фильтры", что-то делаем с массивом (разворачиваем, например)
f1' p = when p . M.reverse
f2' p = when p . M.reverse
-- эти функции из тех, что вы называете "в большой монаде", делаем из них
-- такие, которые вы считаете идиоматическими, т.е. копирующие:
f1 :: V.Vector v a => Bool -> v a -> v a
f1 p = V.modify $ f1' p
f2 :: V.Vector v a => Bool -> v a -> v a
f2 p = V.modify $ f2' p
-- комбинатор для производства таких функций из монадических устроен так:
-- modify f = new . New.modify f . clone
-- эта функция копирует (clone) переданный массив в свежеразмещенный
-- т.е. такой про который мы знаем, что ссылка на него есть только
-- в данной функции, а значит его можно "заморозить" в иммутабельный
-- без копирования (new).
-- Ну, теперь выстраиваем конвейер:
test1 p1 p2 = f1 p1 . f2 p2
-- без оптимизации тут два выделения памяти для массива.
-- но есть оптимизация. В библиотеке (в числе прочих) прописано правило
-- "clone/new [Vector]" forall p. clone (new p) = p
-- буквально означающее, что копировать свежеразмещенный массив не нужно
В результате, конвейер скомпилируется во что-то вроде этого (хаскель, восстановленный из core):
test1 p1 p2 v1 = runST $ do
let x1 = length v1
s <- new x1 -- первоначальное выделение памяти под мутабельный массив
copy v1 s -- копирование в него переданного иммутабельного массиваif p2
then
M.reverse s -- разворот in-placeif p1
then
M.reverse s
unsafeFreeze s -- "заморозка" без копирования.else
unsafeFreeze s
else if p1
then
M.reverse s
unsafeFreeze s
else unsafeFreeze s
Т.е. только размещение исходных данных, никаких ненужных копирований нет.
'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
Re[54]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>А то, что компилятор Хаскеля не проверяет исходники какой-нибудь библиотеки на диске, не используемой в данном проекте, случаем не снижает уровень типизации?
Снижало бы, если бы этот код не проверялся тайпчекером. Но он проверялся. Ведь обобщенный код на хаскеле проверяется для всех типов, а не для конкретных и только после специализации.
Утверждения о проверке только используемого кода на C++ просто некорректны. Обобщенный код, который используется в другом обобщенном коде не проверяется. Библиотеки в которых вообще код только обобщенный, т.е. в случае C++ непроверяемый — распространенное явление для языков с нормальным параметрическим полиморфизмом.
_>Я физически не мог называть скорость обобщённого кода в C++ оправданием нетипизированности, хотя бы потому, что не раз говорил, что не считаю его нетипизированным.
Вы действительно не называете их "нетипизированными", однако их отличие от всех прочих систем обобщенного программирования в аспекте типизации вроде как признаете, нет?
_>Так что ценой за сочетание мощи+скорости шаблонов в C++ является как раз отсутствие раздельной компиляции.
Это, в основном, соответствует действительности, вот только отсутствие раздельной компиляции и вообще генеративная природа параметризации никак не связана с типизированностью. Я же говорю, существует системы обобщенного программирования вроде SML-ных функторов (именно SML-ных, окамловых это не касается) и F#-ных инлайн-функций, которые точно так же меняют проблемы с раздельной компиляцией на производительность как и плюсовые шаблоны, но полностью типизированы.
_>Собственно в Хаскеле всё тоже самое, разве что добавлена возможность получить медленный код с раздельной компиляций.
Там добавлена возможность получать достаточно быстрый код с преимущественно раздельной компиляцией, что вовсе не то же самое, а полностью противоположно ситуации с C++ в котором никакого компромисса между раздельностью компиляции и производительностью построить нельзя.
'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
Re[32]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Чистый снаружи, но не сам по себе.
Что это значит? Это можно как-то раскрыть?
_>Это имеется в виду что-то типа возвращения из функции нескольких разных лямбд, каждая из которых захватывает одну и ту же локальную переменную из нашей функции? Что-то вообще ни разу не попадался мне на практике такой вариант, хотя лямбды использую с удовольствием.
Потому, что в императивных языках с кое-какими функциональными фичами лямбды/замыкания используют совсем не так, как в ФЯ, где они основной инструмент. Это определяется в числе прочего и принципиально разным уровнем поддержки.
Контейнеры с функциями совершенно нормальный случай. См. аппликативные функторы, например.
_>Но в любом случае решение тут тоже очевидно — shared_ptr.
Еще раз: счетчики тормозят и не работают с циклическими ссылками, которые постоянно встречаются в ФП.
_>Интересная мысль. ) А вот скажем известный набор паттернов проектирования — это высокоуровневый или низкоуровневый код? )
Это низкоуровневый код. Если я правильно понял о чем идет речь конечно (о кодогенерации а-ля Александреску).
_>Так просто в данном случае весь "ужас ужас ужас" не в самой функции, а в коде её использования (который не был показан).
Ну так покажите. А то я опять какой-то "страшный" монадический код покажу и опять окажется, что весь ужас в еще непоказанном. Нету у меня чувства жуткого монадического кода — тут без эксперта по ужасам монад никак не получится.
_>В C++ то нет особой разницы в использование функции int f(int) и void f(int&), а в Хаскеле во втором случае появляется та самая жуть...
А в бестиповых языках, бывает, нет особой разницы между числом и строкой, можно их складывать, например. Вот это удобство!
'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
Re[32]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Над этим числодроблением можно точно также надстроить абстракции (бесплатные/дешёвые), более близкие к конкретной предметной области. Например Eigen+МКЭ.
Будто бы МКЭ какое-то особенное числодробление.
EP>В C++ по-другому. Там не "то что дорого — запрещено", а "you don't pay for what you don't use". EP>За то что нужно платить — выключено по умолчанию, но это не означает что оно полностью запрещено.
Проблема в том, что за множество высокоуровневых инструментов нужно платить даже при неиспользовании, т.е. когда они "выключены" — это им дорогу в низкоуровневые языки вроде C++ и закрывает.
K>>В большинстве низкоуровневого кода — да, в большинстве идиоматического ФП-кода — нет. EP>Например C#/Java — это какие по-вашему? Низкоуровневые?
Разговор был вообще-то про ФП, с натяжкой ФЯ можно разве что C# посчитать.
Уровень у них повыше, чем у C++. И что? Писать на C++ в стиле C# и Явы (особенно в современном, когда популярны иммутабельные "объекты" и т.д.) примерно настолько же затруднительно, насколько и в ФП стиле. Собственно, ООП нуждается в точном компактифицирующем кучу GC не меньше ФП.
EP>Это достаточно искусственный/специфичный пример.
На самом деле, когда мы программируем в ФП стиле, комбинируя функции — это достаточно типичный случай.
EP>А для чего востребован сам этот ФП код? ("этот" — о котором идёт речь, с дорогими абстракциями. (отдельные ФП элементы действительно имеют полезные свойства, необходимые при решении практических задач, и при этом бесплатны/дёшевы)).
Вы постоянно уводите тему с поддержки ФП в C++ на ненужность ФП. Я не считаю нужность ФП-инструментария чем-то несомненным и не подлежащим обсуждению и при других обстоятельствах с удоольствием бы об этом поговорил, но в данной ветке — это просто сведение дискуссии с рельсов. Если даже вы обоснуете ненужность какого-то ФП-средства — это не будет означать наличие его поддержки там, где его нет. Если вы доказываете, что ФП поддерживается, то на просьбу предъявить X вы должны показать как легко X использовать и как хорошо он оптимизируется, а рассуждать о ненужности X, наоборот, не должны. Т.е. никто этого запретить вам не может, но к теме разговора это отношения не имеет.
EP>Нужно, но не везде и не всегда.
Так можно сказать про что угодно. Проблема в том, что если нужно не везде — где-то все равно нужно, значит поддержка должна быть.
EP>То есть весь вопрос в том, насколько часто у дорогих абстракций нет дешёвых альтернатив.
Полноценных альтернатив у дорогих абстракций не бывает никогда — иначе они (дорогие абстракции) никогда не вышли бы из областей больного воображения в реальный мир. Альтернативы "для бедных" бывают. Насколько часто — зависит от того, насколько "бедным" согласен быть пользователь таких альтернатив.
EP>Память плоская только в пределах массива/структуры.
И где же у указателя эти "пределы"?
EP>Сам указатель не даёт никакого penalty. Он не отражает иерархичность, но не исключает её.
Это какая-то софистика. Если мы будем употреблять указатель как указатель — мы будем биться об стену памяти. Чтоб этого не делать, нужно учитывать то, от чего указатель нас абстрагирует, т.е. разрушить абстракцию. Такая абстракция как раз иерархичность и исключает (из рассмотрения), а рассмотрение иерархичности — исключает такую абстракцию.
EP>Если нужен быстрый код — нужно так или иначе учитывать GCD особенности целевых систем. Например для учёта иерархической организации памяти разрабатываются сache-oblivious алгоритмы. EP>Что и как можно встроить в понятие указатель для отражения иерархичности? Что это даст по сравнению с тем что есть сейчас?
сache-oblivious — это как раз подход к построению абстракций, которые иерархичность памяти учитывают, а вот от конкретных параметров компонент этой иерархии нас абстрагируют.
А вот абстракции "указатель" и "массив" порождают такие тормозные в условиях иерархичной памяти штуки как поиск делением пополам и быстрая сортировка.
'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
Re[33]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
EP>>Если нужен быстрый код — нужно так или иначе учитывать GCD особенности целевых систем. Например для учёта иерархической организации памяти разрабатываются сache-oblivious алгоритмы. EP>>Что и как можно встроить в понятие указатель для отражения иерархичности? Что это даст по сравнению с тем что есть сейчас? K>сache-oblivious — это как раз подход к построению абстракций, которые иерархичность памяти учитывают, а вот от конкретных параметров компонент этой иерархии нас абстрагируют.
Так я об этом и говорю — машину нужно учитывать. Указатель не исключает иерархичность.
K>А вот абстракции "указатель" и "массив" порождают такие тормозные в условиях иерархичной памяти штуки как поиск делением пополам и быстрая сортировка.
Если про бинарный поиск ещё можно поспорить (например при определённых параметрах иерархичности implicit B-tree будет лучше), но чем quicksort не угодила?
Я про quick sort Тони Хоара, а не про ту пародию которую с гордостью показывают хаскелисты. Quick sort это как раз пример сache-oblivious алгоритма.
И, например, std::sort Степанова в SGI STL использует quick_sort как базу (introsort — начинает с quick sort, если достигается threshold глубина то делает переход на heap sort, и в конце всё полируется insertion sort). Уж кто-кто, а Степанов умеет делать быстрые алгоритмы.
Re[33]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
EP>>Нужна ли такая абстракция или нет, приемлема ли такое penalty или нет — естественно зависит от прикладной области. K>Все-таки необходимость таких вещей слабо связана с предметной областью. Предметная область для таких средств — написание хорошо взаимодействующих друг с другом библиотек. Но допустим, что это так. И что нам делать, если язык ленивость нормально не поддерживает? Избегать эту прикладную область?
То есть без ленивости трудно делать библиотеки хорошо взаимодействующие друг с другом?
EP>>Я предполагаю что лучшего решения нет — мутабельные данные (модификация/чтение для которых может пересекаться) требуют синхронизации by-design, поэтому их и нужно избегать. K>И вы сможете без них обходиться?
Обычно синхронизация для мутабельных данных реально требуется только по периметру модуля, а не по всей площади.
Если же вы говорите что ленивость это пища для ФП — то видимо должна использоваться повсеместно, так? И соответственно меж-поточная синхронизация разбрасывается по коду только ради самой дорогой абстракции, а не для решения прикладной задачи. Ничего хорошего в этом нет
Если же ленивость нужна только по периметру, то никакая это не пища ФП.
EP>>Решение аналогичных задач (но через другую функциональность) в других языках достигается куда более дешёвыми средствами. K>Дешевыми в смысле производительности получившегося кода — да. В смысле труда программиста — нет. Аналогичные задачи в низкоуровневых языках решаются ручным слиянием функций, вместо их комбинирования.
"Ручное слияние" это по сути дубликация структуры кода, так? Хотелось бы увидеть пример.
Re[53]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
EP>>Второе крайнее положение есть — type-erasure, но оно достигается специальным кодом, а не компилятором. EP>>Например можно сделать так:
[...] EP>>Оба варианта работают с "callable" типа int(double), без всякого наследоавния. Но в первом случае код шаблона должен быть виден в месте использования (хотя конкретные специализации можно компилировать отдельно), а во втором — раздельная компиляция. K>Речь идет о разных вариантах компиляции одного и того же кода. А тут код отличается.
Так я же и говорю, что автоматизма нет — нужна специальная обвёртка с type-erasure над шаблонной версией. Это безусловно недостаток, я всего лишь показываю ручную альтернативу.
Кстати, вот тут говорится что при использовании нескольких уровней (4-6) type-class'ов в GHC начинаются runtime тормоза, так как компилятор не может это переварить.
Re[31]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
EP>>Так это и есть работающий код: EP>>
EP>>tail -n 6 main.cpp
EP>>
K>Ядерный реактор условно не показан. K>Все же можно посмотреть полный код, который можно скомпилировать и запустить?
В коде то, что я описал ранее — Lazy<T>, LazyList<T>, каррированные функции и named operator'ы.
Код не показываю только потому, что я его не оптимизировал (нет ни времени, ни мотивации), а начинать сравнение скорости 100-строковой реализации vs optimized code мне бы не хотелось.
Нечто подобное есть в outdated библиотеке FC++.
EP>>Вот только смысла так писать нет — итеративная версия будет и проще и быстрее. EP>>Но если нужна действительно быстрая версия, то посыпание синтаксическим сахром ничем не поможет — нужно использовать подходящий алгоритм, типа ((1,1),(1,0))^n*(1,0). K>У вас что, есть волшебный рецепт переписывания любого ФП-кода в "итеративный" или вообще "подходящий"?
В том то и дело что нет. И даже нет волшебного рецепта переписывания "итеративного" кода в "подходящий"/"оптимальный".
K>Понятно, что считать числа Фибоначчи именно таким способом бессмысленно. Это просто демонстрация применения ряда идиоматических приемов/языковых средств на минимальном примере.
Именно об этом я и говорю. Нужно знать алгоритмы, нужно знать машину, нужно знать mapping между ними — и только тогда можно получить эффективный код.
Так уж получилось, что на современные машины ФП код мэппится плохо. Да что там машины, многие полезные и практичные алгоритмы плохо мэппятся на чистый ФП (без всяких хаков типа STArray), на ровном месте добавляется логарифмическое penalty к complexity.
Re[31]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
EP>>"обычно небесплатен" — в том то и дело, что он никому не обязан быть "платным". K>Как только придумаете небесплатные аналоги — так сразу и будет не обязан. А пока, в данной нам в ощущениях реальности эти средства вполне "платные".
Так есть ведь аналоги. Вместо создания списка, в котором косвенность на косвенности, аллокации и thunk'и — в ряде случаев достаточно обычного плоского массива.
K>>>Если вы утверждаете, что поддержка ФП в языке есть, то аргументы вроде "писать ФП ради самого ФП — нет смысла" и "вместо ФП можно использовать более эффективные техники" невалидные. Они пришлись бы к месту, если бы вы доказывали его ненужность, но доказанная ненужность ФП опять таки не означает доказанного наличия ФП там, где его нет. EP>>Я говорю что использовать ФП ВЕЗДЕ нет смысла, так как в ряде случаев есть более эффективные и зачастую более понятные техники. Использование ФП там где оно добавляет тормозов на ровном месте, затрудняет понимание и не даёт никаких преимуществ — это и есть "писать ФП ради самого ФП". EP>>Каким образом вы решили что аргумент "писать ФП ради самого ФП — нет смысла" применим только при доказательстве ненужности всего ФП — для меня остаётся загадкой K>Ну так ваше "писать ФП ради ФП нет смысла" — это же просто отговорка. Если что-то в плюсах поддерживается слабо — значит это ФП ради ФП — смысла не имеет. А если поддерживается — сразу имеет смысл.
Какая отговорка? Ленивые вычисления — реализуются, UFP — решена. И да, использовать эти средства везде — нет смысла. В тех местах где performance penalty допустима, и если эти средства действительно дают преимущества — то их нужно использовать.
Если же вся программа целиком будет состоять только из дорогих ФП элементов, то действительно лучше взять язык заточенный под это
K>Допустим, ленивость не нужна, гарантии доступности для захваченного в замыкания (а значит и лямбды) не нужны. А что тогда нужно? Вы список какой-нибудь можете составить — какие же ФП-средства нужны, в котором более 0 пунктов?
Нужны где? По всему коду? Например:
Чистые функции: действительно полезно и удобно, это должен быть default. Причём внутри они могут что-то мутировать, создавать, удалять — также как и State Monad. (если чистые не получаются — можно регулярные (результат зависит только от аргументов, но могут быть side-effect'ы)).
Функции высшего порядка и first-class functions: мощная и полезная вещь.
Referential transparency: см. STL.
Замыкания: удобны, особенно при захвате большого количества переменных.
И это всё бесплатно, или крайне дёшево (зависит от компилятора) — так как всё прекрасно inline'ится.
Где-то наверху в calltree, можно использовать какие-то дорогие штуки, но не нужно разбрасывать их по всему коду без необходимости.
Re[31]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>>>Тормозит по сравнению с чем? EP>>Тормозит по сравнению с простейшей итеративной версией, которую можно объяснить даже ребёнку. K>Ну я и говорю. Под поддержкой фп понимается "это все ненужно — можно же написать нетормозящую итеративную версию!".
Я вам показал как поддерживается ФП. И да, для практических целей тормозящая реализация fibs на ленивых списках не нужна.
Но из этого никак не следует что "под поддержкой фп понимается это все ненужно"
K>>>Вот когда предоставите для сравнения control-structure для связывания комбинаторов с комбинаторами с аналогичной функциональностью (поддержка раннего завершения, циклов, автомемоизации, потокобезопасность и т.д.) тогда и сравним. EP>>Зачем всё это при вычислении простейших задач типа чисел Фибоначчи? K>Для более сложных задач. Впрочем подход обычный: поддержка ФП инструментария опять оказалась риторическим вопросом "зачем все это нужно?"
Для более сложных задач — возможно. Но вопрос был про простые задачи.
Было заявлено что fibs это идиоматический hello world, и ленивость это пища ФП. Если же для простейших задач ленивость вредна — то видимо это и не "пища ФП", ведь так?
Re[35]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Конкретный ответ получить можно. Вот он: K>...
О, интересное решение, спасибо. Значит мы пишем наши функции всё же монадным способом, а потом пакуем их в копирующие обёртки, с расчётом на то, что оптимизация выкинет эти обёртки на месте стыка.
Т.е. автоматически (обычным красивым кодом) добиться быстродействия в Хаскеле нельзя, но если специально заняться этим вопросом, то руками можно много чего оптимизировать...
K>В результате, конвейер скомпилируется во что-то вроде этого (хаскель, восстановленный из core): K>... K>Т.е. только размещение исходных данных, никаких ненужных копирований нет.
Ну вообще то одно лишнее копирование всё же есть. Но я понимаю о чём вы — что при 10-и функция будет всё равно это же одно лишнее копирование, а не 10.