Здравствуйте, Sinclair, Вы писали:
S>Проблема в том, что вы заранее не знаете, где какой код. У вас может смешиваться код, "знающий", что такое optional, и код, который ничего о нём не знает. Чтобы это решить, вам придётся либо явно описывать упаковку/распаковку в перегрузках LibFunc, либо всё-таки сделать из Optional монаду.
Ну так если в вашем пример функции уже есть упаковка в optional, то почему бы не быть и распаковке? ) Причём поясню, что это имеет смысл только если LibFunc действительно как-то работает с optional (т.е. например может генерировать nothing), а иначе обычная функция отлично работает. И это будет не перегрузка, а единственный вариант.
S>Это потому, что вы задумали неправильно. Ваш код работает ровно на одном тестовом примере, а шаг вправо/влево приводит к неожиданностям.
Никаких неожиданностей. Всё работает предсказуемо и корректно. Собственно даже можно получить прямо из этого требуемый результат, если ввести функцию join (что для optional тривиально). Но смысла в этом никакого нет.
S>Пока нет. Но и код неуниверсальный. Когда вы его почините, у вас будет полная монада.
Правильно ли я понимаю, что из скажем Хаскеля надо удалить все функции fmap (как не универсальные) и соответственно само понятие функтора? )
S>Это и есть функтор (в математическом смысле, а не в смысле С++). Какая разница, в какой момент он работает? Важна семантика.
Ну в общем то да. Просто при разговоре о монадах/функторах в смысле программирования постоянно происходит оглядывание на Хаскель, а в нём такое нереализуемо. Поэтому возможно и кажется странным.
Re[21]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
EP>>C++ предоставляет выбор — можно сделать move, можно copy, можно ref-counted, а можно вообще зарегистрировать deleter в scope повыше. Причём это работает для любых ресурсов, а не только для памяти как в языках с GC. K>Выбор-то он предоставляет, вот только того варианта, который лучше всего подходит для ФП кода — нормального ГЦ — среди предложенных нет.
В ISO C++11 определён интерфейс для консервативного GC. Более того готовые реализации есть уже более 10 лет, вот только GC для C++ не популярен, что говорит об его необходимости.
K>В принципе, F# функция могла бы имплементировать IDisposable, если захватывает в замыкание что-то имплементирующее IDisposable. Технических проблем тут, на первый взгляд, нет, но это не сделано. Пришлось мне это сделать самому, отнаследовавшись от FSharpFunc: K>
K> public static class DispFun
K> {
K> public static FSharpFunc<T, R> Create<T, R>(IDisposable disp, FSharpFunc<T, R> f)
K> {
K> return new DispFun<T, R>(disp, f);
K> }
K>
1. Везде придётся расставлять using'и вручную?
2. Если забыть using, то будет не-детерминированный finalize? Т.е. компилятор не сделает это сам или не подскажет ошибкой?
3. Если такое замыкание станет частью другого объекта, то сам этот объект и все его пользователи — транзитивно становятся IDisposable. Что предполагается делать в этом случае? Вручную проверять все места или всё же компилятор чем-то поможет?
4. DispFun работает только для функций с одним аргументом?
EP>>Работающего [&] при передачи вверх естественно не будет. Но говорить что работающий [&] это необходимое условие для решения UFP — неправильно. Вот например цитата из wiki: EP>>[q] EP>>Another solution is to simply copy the value of the variables into the closure at the time the closure is created. EP>> В разных языках разные подходы K>С практической точки зрения, наиболее полезный способ — это именно нормальный [&].
С практической точки зрения, в большинстве случаев нужен именно move, а не GC. GC прогибается под нагрузкой, поэтому и занимаются такими вещами как off-heap cache, escape analisys, раскладывание структур вручную по массивам байт и т.п.
Т.е. GC в одном месте немного упрощает использование, но платить за это всё же приходится в другом.
K>>>При сколько нибудь существенном использовании ФП вы устанете ждать, пока там что-то копируется, EP>>Копировать необязательно, часто достаточно move. EP>>Во-первых в большинстве полезных случаев ref-counted не нужен K>С моей точки зрения, копирование и передача владения удовлетворяют всяким предельным и маргинальным случаям. K>Какой смысл вообще связываться с ФП, если оно останется на игрушечном уровне и придется ходить держась за стенку? K>Никакого нормального перехода к высокоуровневому коду не будет, если программист не получит абстракции, которые просто работают.
Высокоуровневый код возможен не только с чистым ФП. И я не вижу смысла в использовании ФП абстракций во всех местах (т.е. ФП, только ФП, и ради ФП).
Цель абстракции — описать мир, причём эффективно, а не придумать его. Пока у нас у всех фон-Нейманавские машины — чистый ФП будет находится на академической обочине.
EP>>код не замусоривается: EP>>
x->>>baz();
EP>>
K>Конечно он замусоривается, если для чего-то использующегося как правило нужно писать и читать дополнительную писанину, а для исключений — не нужно. Должно быть наоборот.
Разделение владения ресурсом из одного scope между несколькими замыканиями переживающими этот scope — это скорее исключение в C++, чем default-use-case. И это не потому что нет GC, а потому что на практике требуется редко (и для таких случаев вполне подходит ref-counting).
K>>>Не говоря уж о том, что в ФП циклические ссылки в порядке вещей. EP>>Они действительно возможны, но разве это прям в порядке вещей? Особенно учитывая иммутабельность. K>Да, в порядке вещей. Особенно учитывая, что ленивость — это такая замаскированная мутабельность, которая позволяет сконструировать иммутабельную структуру с элементами "из будущего".
ФП ведь не предполагает ленивость by-default?
K>>>Вообще говоря, тут утечка ресурса. EP>>Где? K>Вы возвращаете функцию, владеющую открытым ресурсом. В реальном ФП коде ее время жизни будет определятся динамически, а не статически по лексическому скоупу и т.д.
Динамическое время жизни это разве обязательная характеристика "реального ФП"?
На практике, динамическое время жизни объектов обычно вытекает из внешней неопределённости, а не алгоритмической структуры. Например при реакции на внешние события.
Возможно если ещё и сам язык добавляет неопределённости во время жизни — то это не самая лучшая абстракция?
K>В результате никакого преимущества перед финализацией ГЦ тут нет. Вообще, я считаю, что захват и передача вверх портит все автоматическое управление ресурсами с помощью стека. Это для нее плохой сценарий, на который такая система не расчитана, а в ФП он типичный.
Такой сценарий типичен при асинхронной работе с сетью (замыкание регистрируются как реакция на внешнее событие), например через Boost.Asio. Там как раз типичный UFP — и никаких проблем с этим нет.
Более того — если сделать re-inversion of control с помощью stackful coroutines, то UFP уходит за кулисы.
EP>>Это только иллюстрация проблемы UFP, естественно в большинстве use-case'ов это замыкание будет вызываться несколько раз, и возможно будет передаваться ещё выше. Например: EP>>ccode EP>>Как сделать подобное в C# или Haskell? K>См. мой код на F# выше.
То есть будет возвращаться не само замыкание, а завёрнутое во wrapper добавляющий IDisposable, с ограничением на сигнатуру замыкания в один аргумент, с необходимостью ручной расстановки using, причём все объекты в которых находится это замыкание транзитивно становятся IDisposable, со всеми вытекающими.
И разве это приемлемое решение UFP? Это же фактически контроль за временем жизни на каждом уровне вручную.
ОК, с C#/F# понятно, а как это будет выглядеть в Haskell? (код не обязательно)
EP>>В языках где есть готовые решения только для памяти, но нет ничего для остальных ресурсов — действительно, такой номер не пройдёт. K>И чем в этом общем случае будет лучше ваше средство управления ресурсами, которое даже для памяти решения не дает?
C++ даёт решение и для памяти, и для ресурсов, причём решение единообразное. Да оно менее автоматическое чем GC, но работает для всех ресурсов, и не имеет минусов GC.
K>Более-менее осмысленное решение, это делать функцию вида ЭтоНамНадоЧтобПолучитьРесурс -> (ОткрытыйРесурс -> Результат) -> Результат, которая сначала собирает через второй аргумент функции, которые что-то будут с ресурсом делать, а потом выполняется сама, открывая ресурс, скармливая всем этим функциям и закрывая его. Т.е. те самые брекеты и их развитие вроде pipes-safe и resourcet. EP>>Часто открытие/закрытие ресурсов может быть накладным, не говоря о том что может быть вообще недопустимо (например квота на сессии). K>Так при таком подходе открытие/закрытие один раз и происходит.
То есть предполагается что все действия над ресурсом должны обязательно собраться в одном месте? Это серьёзное ограничение.
Re[60]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
EP>>Поэтому, естественно, в случае C++/D лучше воспользоваться встроенными исключениями в язык (причём оптимизированными). _>Ну думаю и для optional можно найти узкое место применения в C++, но естественно не в виде монады или чего-то подобного, а вот прямо как в Boost'e.
Для использования "в виде монады" подойдёт просто звёздочка, без предварительной проверки. При необходимости звёздочка вызовет встроенную "монаду" — Exception.
_>Например для случая, когда nothing является вполне себе регулярным результатом, а не признаком исключительной ситуации.
Или другой use-case: отложенная инициализация объекта без default constructor.
_>Только тогда планировать его в код изначально и использовать через if.
Да, в таких случаях обычно ещё и else есть с нетривиальным кодом. То есть использование в стиле алгебраического типа — и это даже не функтор Maybe.
Re[42]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Ага, но опять же в дело вступает вопрос соотношения объёмов. Не думаю, что можно считать хорошей программой такую, в которой большую часть занимают абстракции, а не занятия собственно делом. А делом у нас занимаются как раз вызовы системного апи, последовательности которых нам и надо будет выписывать (и побольше, чем введений всяких абстракций). А именно этот процесс и сопровождается в Хаскеле лишними (по отношению к обычным императивным языкам) плясками.
Странная точка зрения. Вы что, серьёзно считаете, что в каком-нибудь VirtualDub "занятия делом" — это только обращения к *Alloc() и WriteFile()? А я-то думал, что там рулят собственно кодеки и фильтры.
Или, скажем, SQL сервер — там вообще кроме WriteFile ничего нету, в сокеты да в диск.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[58]: Есть ли вещи, которые вы прницпиально не понимаете...
_>Ну так если в вашем пример функции уже есть упаковка в optional, то почему бы не быть и распаковке? ) Причём поясню, что это имеет смысл только если LibFunc действительно как-то работает с optional (т.е. например может генерировать nothing), а иначе обычная функция отлично работает. И это будет не перегрузка, а единственный вариант. S>>Это потому, что вы задумали неправильно. Ваш код работает ровно на одном тестовом примере, а шаг вправо/влево приводит к неожиданностям. _>Никаких неожиданностей. Всё работает предсказуемо и корректно.
Для вас — конечно нет никаких неожиданностей. Потому, что вы не планировали пользоваться вашим кодом, а писали его в качестве упражнения. А для обычных пользователей получить optional<optional<double>> будет весьма неожиданно.
И реальные функции, которые встречаются в реальной жизни, будут устроены не как arg*10, а как что-то вроде
public Manager? GetManagerById(int managerID)
{
var dataRow = Repository.ManagerTable.GetByID(managerID);
if (DataRow == null)
return null;
else
return Manager.CreateFromDataRow(dataRow);
}
Вот такая функция совершенно случайно может получить int? аргумент — например, у нас есть свойство Customer.PreferredManagerID, которое оказалось nullable. Наличие монады позволило бы нам писать var PreferredManager = GetManagerById(customer.PreferredManagerID). Ваш функтор вернёт Nullable<Nullable<Manager>>, что вовсе не то, чего мы ожидаем.
Вы предлагаете чинить это при помоши замены типа аргумента, и переписывания нашей функции примерно вот в это:
public Manager? GetManagerById(int? managerID)
{
if (!managerID.HasValue)
return null;
var dataRow = Repository.ManagerTable.GetByID(managerID.Value);
if (DataRow == null)
return null;
else
return Manager.CreateFromDataRow(dataRow);
}
Это называется "дублирование логики", и именно этого передовики производства стараются избегать. Зачем нам эти строчки в каждой функции, если они уже есть в монаде? _>Правильно ли я понимаю, что из скажем Хаскеля надо удалить все функции fmap (как не универсальные) и соответственно само понятие функтора? )
Зачем? Эти функции прекрасно решают поставленные перед ними задачи. А ваша — нет, не решает.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[22]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Разделение владения ресурсом из одного scope между несколькими замыканиями переживающими этот scope — это скорее исключение в C++, чем default-use-case. И это не потому что нет GC, а потому что на практике требуется редко (и для таких случаев вполне подходит ref-counting).
Это именно потому, что нет GC — очень трудно писать, еще труднее отладить и совершенно невозможно майнтейнить такой код.
Там где есть GC, циклические ссылки и разделение владения ресурсом используется направо-налево даже не задумываясь.
Именно по этой причине возврат замыкания из using вещь крайне странная.
Re[22]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>В ISO C++11 определён интерфейс для консервативного GC.
А смысл? Консервативный ГЦ бесполезен. Это практически никаких плюсов точного сборщика, практически все его минусы да еще куча минусов сверх того.
EP>1. Везде придётся расставлять using'и вручную?
писать use вместо let
EP>2. Если забыть using, то будет не-детерминированный finalize? Т.е. компилятор не сделает это сам или не подскажет ошибкой?
Нет, сам не сделает.
EP>3. Если такое замыкание станет частью другого объекта, то сам этот объект и все его пользователи — транзитивно становятся IDisposable. Что предполагается делать в этом случае? Вручную проверять все места или всё же компилятор чем-то поможет?
Нет, не поможет.
EP>4. DispFun работает только для функций с одним аргументом?
Это же F#-функция. Там только функции с одним аргументом — других нет.
Все перечисленное, впрочем, не важно. Никто так не делает, потому что передача куда-то замыкания с захваченным ресурсом — это проблема которая в общем случае ничем не лечится. А ради вырожденного юзкейса никто не станет огород городить. Это просто демонстрация технической возможности и ответ на вопрос о том, "где using писать?"
EP>С практической точки зрения, в большинстве случаев нужен именно move, а не GC.
Не в ФП.
EP>GC прогибается под нагрузкой,
Ну а вы что хотели? За высокоуровневость нужно платить.
EP>поэтому и занимаются такими вещами как off-heap cache, escape analisys, раскладывание структур вручную по массивам байт и т.п.
Не сказал бы, что эскейп-анализ сильно на нагрузку на ГЦ влияет. То, что устраняет ЭА обычно короткоживущие объекты, для ГЦ в основном безвредные. Просто какой-нибудь объект Complex в куче или пара даблов в регистрах — это по производительности смешно даже сравнивать.
EP>Т.е. GC в одном месте немного упрощает использование, но платить за это всё же приходится в другом.
Естественно. Тут вместо GC можно подставить что угодно и будет правильно — это просто трюизм. Речь о том, что если мы говорим о поддержке ФП, то он облегчает в таком месте, без облегчения в котором никто с ФП даже связываться не станет. В C++ просто противоположный выбор сделан, который облегчает там, где нужно для естественной области применимости C++, а там где нужно облегчать для ФП — наоборот осложняет.
EP>Высокоуровневый код возможен не только с чистым ФП. И я не вижу смысла в использовании ФП абстракций во всех местах (т.е. ФП, только ФП, и ради ФП). EP>Цель абстракции — описать мир, причём эффективно, а не придумать его. Пока у нас у всех фон-Нейманавские машины — чистый ФП будет находится на академической обочине.
Мы вообще тут говорили не о том нужно ФП или нет, а о том, что есть какие-то минимальные требования, для того чтоб можно было сказать "есть поддержка ФП" и которым C++ не соответствует. Отход от темы начался заявлением, что в C++ все якобы решено. Закончилось все ожидаемо: заявлением о том, что ФП не нужно. Видимо это и есть подразумеваемое решение.
EP>Разделение владения ресурсом из одного scope между несколькими замыканиями переживающими этот scope — это скорее исключение в C++, чем default-use-case.
Ну разумеется. Потому, что C++ не ФЯ.
EP>И это не потому что нет GC, а потому что на практике требуется редко (и для таких случаев вполне подходит ref-counting).
Нет, именно поэтому. Потому, что хоть сколько-нибудь неигрушечных реализаций высокоуровневых языков без ГЦ не бывает. Две известных мне попытки реализовать высокоуровневый язык без ГЦ провалились.
EP>ФП ведь не предполагает ленивость by-default?
Мы вроде бы в соседней ветке выяснили, что без ленивости комбинирование функций теряет смысл из-за непрактичности. Также из-за непрактичности без ленивости теряют смысл иммутабельность и персистентные структуры данных. После этого от ФП уже ничего и не остается.
Другое дело, что это иная степень необходимости. Обсуждаемая здесь UFP — это, допустим, кислород, а ленивость — пища. Т.е. без нее в функциональном программировании как-то существовать можно, но плохо и недолго.
EP>Динамическое время жизни это разве обязательная характеристика "реального ФП"?
Ну я же рассказывал о печальном опыте MLKit. Вот краткая выжимка. Ключевой момент "We believe that it is the case that common SML idioms tend to work better under GC than under regions." Обратите внимание, что речь идет о SML, достаточно императивном и относительно низкоуровневом, если с каким-нибудь хаскелем сравнивать. В более продвинутом ФЯ все будет только хуже. Реализация Хаскеля, в которой пытались обойтись регионами без ГЦ тоже была (JHC), и тоже неудачная.
EP>Возможно если ещё и сам язык добавляет неопределённости во время жизни — то это не самая лучшая абстракция?
Ну, абстракция — это тоже трейдофф. При полной определенности никакой абстракции не останется.
EP>Такой сценарий типичен при асинхронной работе с сетью (замыкание регистрируются как реакция на внешнее событие), например через Boost.Asio. Там как раз типичный UFP — и никаких проблем с этим нет.
Непонятно, правда, зачем нужна асинхронность I/O через колбек-лапшу в высокоуровневом языке.
Если очень постараться, можно, конечно, пользу и от чемодана без ручки какую-нибудь найти, вот только какой-то поддерживаемый юзкейс не означает, что неподдерживаемые — не проблема.
EP>То есть будет возвращаться не само замыкание, а завёрнутое во wrapper добавляющий IDisposable
Не понятно, что за "не само замыкание". Все F#-функции такие вот наследники. Из кода, который я опубликовал вроде видно, что добавлятель IDisposable принимает 'a -> 'b и возвращает 'a -> 'b. Что не так-то?
EP>с ограничением на сигнатуру замыкания в один аргумент
Странная претензия в случае F#. А скольких аргументов должны быть замыкания? И с какой стати это ограничение? Если тут возникло какое-то недопонимание, на всякий случай поясню, что вот это
fun s -> DispFun.Create(f, fun t ->
(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
f.Write(s + t + "!")
x + y)
let bar () =
let f = foo () "!"
use d = DispFun.ToDisposable(f)
f "OMG"(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
f "OMG!"
f "OMG!!"
и вот это
DispFun.Create(f, fun s t ->
(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
f.Write(s + t + "!")
x + y)
let bar () =
let f = foo ()
use d = DispFun.ToDisposable(f)
f "OMG" "!"(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
f "OMG!" "!"
f "OMG!!" "!"
будет работать.
EP>И разве это приемлемое решение UFP?
Это вообще не решение UFP. UFP — это работающий [&] только и всего. Понятно, что тема эта для плюсистов больная, но это не значит, что нужно перескакивать с нее то на копирование (как будто оно когда-то представляло из себя что-то проблемное для реализации), то на prompt finalization (я понимаю, что на это переводится любой разговор о C++ в ста случаях из ста), то на ненужность ФП. Вы можете, конечно, утверждать, что высокоуровневое программирование не нужно (учитывая, что практически все высокоуровневые фичи: ленивость, континьюейшены и т.д., а не только решение UFP создают какие-то сложности той или иной степени тяжести для prompt finalization) но как это связано с тем, что по вашему заявлению в плюсах все это якобы решено?
EP>Это же фактически контроль за временем жизни на каждом уровне вручную.
На каждом уровне, на котором вы хотите закрывать ресурс.
Впрочем, как я уже заметил выше, обсуждать такие извращения с практической точки зрения нет смысла.
EP>а как это будет выглядеть в Haskell? (код не обязательно)
Это решение вполне приемлемое для низкоуровневых языков и соответствующих идиом, но для идиоматического ФП это вообще не решение.
EP>Да оно менее автоматическое чем GC, но работает для всех ресурсов, и не имеет минусов GC.
Да обсуждаемая проблема даже не в недостаточной автоматизированности (хотя, кончено, если программист начнет управлять памятью в ФП вручную — он от ФП устанет за один вечер и на всю жизнь). Я же говорю, существует серьезный ресерч по большей автоматизации/увеличению гибкости такого подхода. Тем не менее, даже такая существенно более продвинутая система, как оказалось, для ФП не подходит (в качестве основной).
EP>и не имеет минусов GC.
Во-первых, большинство минусов у способов управления динамической памятью общие. Их — этих минусов — только в каком-нибудь Фортране 77 нет.
Во-вторых, плюсов GC тоже не будет. И, что важно в обсуждаемом случае, как раз тех, которые для ФП и важны.
EP>То есть предполагается что все действия над ресурсом должны обязательно собраться в одном месте? Это серьёзное ограничение.
Ну да. В хаскеле вон вообще все действия "собираются в одном месте".
Да, к вопросу о циклах, изменении "неизменяемых" структур данных и уникальности указателей.
Вот, что представляет из себя в памяти функциональный "хеллоуворлд" — список чисел Фибоначчи после обращения ко второму элементу списка:
и к третьему:
'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[42]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Обобщённым программированием принято называть конкретную реализацию параметрического полиморфизма,
Кем принято? Вот, к примеру, обзор и сравнение средств обобщенного программирования от плюсовиков (разработчиков концептов, контрибьюторов в Boost и т.д.) вовсе это никакая не "конкретная реализация параметрического полиморфизма".
_>используемую в языках типа C++, Java, C#, D и т.п.
Вопрос о существовании параметрического полиморфизма в C++ вообще спорный. Код для любого типа там в типизированном виде не существует, существует только специализированный для конкретных типов. т.е. полиморфизм там ad-hoc.
_>Хааа, ну так если мы поставляем вместе с объектным файлом ещё и по сути исходник (интерфейсный файл с AST), который потом используется для сборки других модулей, то это вообще то даже близко не компиляция. Это скорее просто некая оптимизация процесса сборки для настроек без оптимизации.
Поставляем малую часть исходника в предкомпилированном виде. Очевидно, что вклад в итоговую производительность у кода сильно неравномерный, так что на практике большая честь кода компилируется раздельно.
_>Т.е. получается, что обобщённый код на Хаскеле действительно может компилироваться в самостоятельный двоичный код, но, как тут и указывали, этот код получается тормозной, так что его используют разве что для процесса разработки. Но можно получить и нормальный код, резко забыв про раздельную компиляцию... )))
Не резко, а как раз плавно. Немного забыли про раздельную — а производительность уже и сильно подросла.
_>Ну так на то они и дженерики, а не шаблоны и как следствие имеют намного более слабые возможности. И я здесь совсем не про метапрограммирование, а именно про возможности обобщённого программирования.
В одних случаях слабее, в других — сильнее. Какие у вас, к примеру, претензии к слабости хаскельных "дженериков" (дженериками там называются совсем другие вещи) по части обобщенного программирования, например?
_>Ага, но опять же в дело вступает вопрос соотношения объёмов. Не думаю, что можно считать хорошей программой такую, в которой большую часть занимают абстракции, а не занятия собственно делом. А делом у нас занимаются как раз вызовы системного апи, последовательности которых нам и надо будет выписывать (и побольше, чем введений всяких абстракций). А именно этот процесс и сопровождается в Хаскеле лишними (по отношению к обычным императивным языкам) плясками.
Есть, конечно, мнение, что думать не надо — а надо трясти, педалить побольше кода, который "делает реальные дела" и т.д., но я его не разделяю.
'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[20]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Ммм не собираюсь влезать в вашу с Евгением непосредственную дискуссию, но после прочтение этого сразу же возник вопрос. Вы конечно же знаете, что в C++ кроме операций "передача ссылки" и операции "копирование" возможна ещё и операция "перемещение"? И что она определена например для всех stl коллекции и для них не особо отличима по потреблению ресурсов от передачи ссылки на коллекцию, хотя при этом происходит полная передача владения?
Много ли от "полной передачи владения" пользы в ФП, где коллекции персистентные и типична ситуация, когда на какую-то часть иммутабельной структуры данных куча ссылок от разных ее версий?
'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[11]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Все перечисленное, впрочем, не важно. Никто так не делает, потому что передача куда-то замыкания с захваченным ресурсом — это проблема которая в общем случае ничем не лечится. А ради вырожденного юзкейса никто не станет огород городить.
Это же и есть upwards funarg?
K>Мы вообще тут говорили не о том нужно ФП или нет, а о том, что есть какие-то минимальные требования, для того чтоб можно было сказать "есть поддержка ФП" и которым C++ не соответствует. Отход от темы начался заявлением, что в C++ все якобы решено. Закончилось все ожидаемо: заявлением о том, что ФП не нужно. Видимо это и есть подразумеваемое решение.
Не так. ФП нужно, но далеко не везде. Писать на C++ только в ФП стиле не имеет смысла.
Нужен ленивый map? Пожалуйста:
use(x | transformed(_1 * _1));
Энергичный inplace? Тоже не проблема:
transform(x, begin(x), _1 * _1);
use(x);
EP>>ФП ведь не предполагает ленивость by-default? K>Мы вроде бы в соседней ветке выяснили, что без ленивости комбинирование функций теряет смысл из-за непрактичности.
В том примере нужна не ленивость, а monadic код. Ленивость помогла только в одном случае из двух, и как видно в общем случае не позволяет сделать обычный код монадичным.
Re[43]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Sinclair, Вы писали:
S>Странная точка зрения. Вы что, серьёзно считаете, что в каком-нибудь VirtualDub "занятия делом" — это только обращения к *Alloc() и WriteFile()? А я-то думал, что там рулят собственно кодеки и фильтры. S>Или, скажем, SQL сервер — там вообще кроме WriteFile ничего нету, в сокеты да в диск.
Внутренность кодеков и фильтров — это как раз и есть занятие делом. А вот архитектура плагинов, через которую они подключаются, — это в данном случае абстракция.
Re[59]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Sinclair, Вы писали:
S>Для вас — конечно нет никаких неожиданностей. Потому, что вы не планировали пользоваться вашим кодом, а писали его в качестве упражнения. А для обычных пользователей получить optional<optional<double>> будет весьма неожиданно.
Дааа? ) А почему тогда fmap в том же haskell'е именно так и работает? )))
S>Вот такая функция совершенно случайно может получить int? аргумент — например, у нас есть свойство Customer.PreferredManagerID, которое оказалось nullable. Наличие монады позволило бы нам писать var PreferredManager = GetManagerById(customer.PreferredManagerID). Ваш функтор вернёт Nullable<Nullable<Manager>>, что вовсе не то, чего мы ожидаем.
Такой "GetManagerById(customer.PreferredManagerID)" код — это следствие не "монадности", а встроенных средств компилятора, причём только для этой конкретной монады. А для некой обобщённой монады (может мы не optional хотим, а list) у вас будет код вида "PreferredManager = customer.PreferredManagerID>>=GetManagerById;". Т.е. вы не решили нашу изначальную задачу (неизменность кода при переходе на монаду) вообще.
S>Вы предлагаете чинить это при помоши замены типа аргумента, и переписывания нашей функции примерно вот в это: S>
S>Это называется "дублирование логики", и именно этого передовики производства стараются избегать. Зачем нам эти строчки в каждой функции, если они уже есть в монаде?
Кстати говоря, реакция на null не обязательно может быть в виде передачи его дальше, а вполне возможна выдача какого-то нетривиального результата. Так что функции вида M<R>(T) не являются самым общим случаем, а наиболее универсальным решением является функция вида M<R>(M<T). Причём если мы посмотрим на монады (и не только монады, но и вообще подобные контейнеры) других типов (тот же list например), то там вообще функций работающих над целой монадой может быть больше, чем работающих с внутренним значением.
S>Зачем? Эти функции прекрасно решают поставленные перед ними задачи. А ваша — нет, не решает.
А чем fmap отличается от моего варианта? ) Ну кроме автоматического применения, которое в Хаскеле (и в C++ и в C#) просто не сделать.
Re[43]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Кем принято? Вот, к примеру, обзор и сравнение средств обобщенного программирования от плюсовиков (разработчиков концептов, контрибьюторов в Boost и т.д.) вовсе это никакая не "конкретная реализация параметрического полиморфизма".
И? ) Читаем оттуда:
"The Haskell community uses the term “generic” to describe a form of generative programming with respect to algebraic datatypes (Backhouse et al., 1999; Hinze & Jeuring, 2003; Jeuring & Jansson, 1996). Thus the typical use of the term “generic” with respect to Haskell is somewhat different from our use of the term. However, Haskell does provide support for generic programming as we have defined it here and that is what we present in this section."
K>Вопрос о существовании параметрического полиморфизма в C++ вообще спорный. Код для любого типа там в типизированном виде не существует, существует только специализированный для конкретных типов. т.е. полиморфизм там ad-hoc.
Нормальный там параметрический полиморфизм, просто он времени компиляции. Это не в том смысле что он имеет статическое связывание (такое во многих языках), а в том, что он "исполняется" во время компиляции.
Если мы посмотрим на язык D (развивающий дальше эту концепцию), то там во время компиляции можно вообще выполнять практически произвольный пользовательский код. И это можно использовать для очень разных интересных возможностей. Например можно ввести полноценный (а не встроенный в язык, как Спирит) DSL, файлы которого будут парситься в процессе компиляции некого D кода и это всё вместе будет превращаться в единый бинарники с полными оптимизациями, включая инлайны. Это несколько другой уровень программирования вообще.
Ну а то, что мы имеет в C++, это так сказать зародыш подобной техники. )
K>Не резко, а как раз плавно. Немного забыли про раздельную — а производительность уже и сильно подросла.
Кстати, а есть возможно указывать подобное руками или всё управляется только компилятором, а мы влиять не можем (кроме опций компилятора)? Т.е. можем ли мы сказать что например "вот данная сущность должна инлайниться вообще всегда"
K>В одних случаях слабее, в других — сильнее. Какие у вас, к примеру, претензии к слабости хаскельных "дженериков" (дженериками там называются совсем другие вещи) по части обобщенного программирования, например?
Под дженериками естественно Java/C# подразумевались и они заметно слабее варианта C++ именно по части обобщённого программирования. Что касается Хаскеля, то там вообще другая концепция и сравнивать их напрямую глупо. Однако если посмотреть по набору возможностей, то тут видится приблизительный паритет со своим набором плюсов и минусов с каждой стороны.
P.S. Как я понял, ни одного примера заявленных (вами) преимуществ реализации ST/IO Хаскеля над другими языками мы так и не увидим... Несмотря на мои многочисленные напоминания об этом. Ну что же, ладно, так и запишем и закроем эту тему.
Re[21]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Много ли от "полной передачи владения" пользы в ФП, где коллекции персистентные и типична ситуация, когда на какую-то часть иммутабельной структуры данных куча ссылок от разных ее версий?
Хехе, первая часть этой фразы действительно относится к ФП вообще, а вот вторая относится уже всего лишь к одной из конкретных реализаций в конкретном языке.
В C++ перемещение как раз оптимально для передачи некой иммутабельной коллекции куда-то за пределы контроля стека (внутри которого отлично действует простая константная ссылка), т.к. при этом полностью сохраняется контроль за владением и нет никаких накладных расходов (как при тупом копирование).
А вот если у нас стоит уже другая (довольно специфическая кстати) задачка, в которой нам необходимо иметь множество таких коллекции из одних и тех же элементов (причём элементы имеют не маленький размер), то для этого есть отдельное простейшее решение. Типа vector<shared_ptr<T>>. И кстати перекидывать куда-то "наверх" такую коллекцию с помощью "перемещения" так же очень удобно и выгодно.
Re[23]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
EP>>Динамическое время жизни это разве обязательная характеристика "реального ФП"? K>Ну я же рассказывал о печальном опыте MLKit. Вот краткая выжимка. Ключевой момент "We believe that it is the case that common SML idioms tend to work better under GC than under regions."
Насколько я понял, именно опыта там и не было. Вот что написано перед вышеприведённой цитатой:
MLton does not use region-based memory management; it uses traditional GarbageCollection. We have considered integrating regions with MLton, but in our opinion it is far from clear that regions would provide MLton with improved performance, while they would certainly add a lot of complexity to the compiler and complicate reasoning about and achieving SpaceSafety. Region-based memory management and garbage collection have different strengths and weaknesses; it’s pretty easy to come up with programs that do significantly better under regions than under GC, and vice versa.
То есть они рассматривали, но не пробовали.
EP>>И разве это приемлемое решение UFP? K>Это вообще не решение UFP. UFP — это работающий [&] только и всего. Понятно, что тема эта для плюсистов больная, но это не значит,
Про "неработающие" замыкания в C++ из-за UFP любители GC мне тут уже третий раз говорят, хотя "плюсисты" не имеют с этим проблем. На деле же оказывается что работают, и не только для памяти.
K>что нужно перескакивать с нее то на копирование (как будто оно когда-то представляло из себя что-то проблемное для реализации),
В примере с файлом как раз и использовалось копирование(ref_counted)/перемещение. Повторить без ручного контроля на каждом уровне в F# не получилось.
K>то на prompt finalization (я понимаю, что на это переводится любой разговор о C++ в ста случаях из ста), то на ненужность ФП.
ФП нужно, но не везде.
K>Вы можете, конечно, утверждать, что высокоуровневое программирование не нужно (учитывая, что практически все высокоуровневые фичи: ленивость, континьюейшены и т.д., а не только решение UFP создают какие-то сложности той или иной степени тяжести для prompt finalization)
Никто не говорил что высокоуровневое программирование не нужно. Высокоуровневое это далеко не только ФП.
K>но как это связано с тем, что по вашему заявлению в плюсах все это якобы решено?
Как минимум есть решение UFP в виде копирования (что является именно решением согласно wiki). Плюс есть другие варианты.
Причём это работает не только для памяти. В языках же с GC с этим начинаются проблемы, и раз там нет нормального решения для этого варианта UFP, то естественно проблема декларируется несущественной/надуманной/нереальной/непрактичной:
EP>>Это же фактически контроль за временем жизни на каждом уровне вручную. K>На каждом уровне, на котором вы хотите закрывать ресурс. K>Впрочем, как я уже заметил выше, обсуждать такие извращения с практической точки зрения нет смысла.
Из дочернего региона в родительский возвращаются сами handle'ы, а не замыкания с хэндлами внутри? Если да — то это же совсем не то, мы же про UFP говорим.
EP>>То есть предполагается что все действия над ресурсом должны обязательно собраться в одном месте? Это серьёзное ограничение. K>Ну да. В хаскеле вон вообще все действия "собираются в одном месте".
Собираются все действия над всеми ресурсами в одном месте, причём они могут быть interleaved, и зависить один от другого.
В описанном же выше варианте действия над одним ресурсом собираются в одном месте, и только в нём. Это и есть ограничение.
Re[23]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Да, к вопросу о циклах, изменении "неизменяемых" структур данных и уникальности указателей. K>Вот, что представляет из себя в памяти функциональный "хеллоуворлд" — список чисел Фибоначчи после обращения ко второму элементу списка:
Хороший пример цикла привнесённого самим языком.
По графу видно насколько неэффективно мэпится решение простой задачи в железо. Кстати, про эффективность, а как thunk'и обновляются в многопоточной среде?
K>Много ли от "полной передачи владения" пользы в ФП, где коллекции персистентные и типична ситуация, когда на какую-то часть иммутабельной структуры данных куча ссылок от разных ее версий?
Это пример разделяемого владения. Если у нас есть персистентная и полностью иммутабельная структура, типа rope, где ссылки и узлы образуют directed acyclic graph — то прекрасно подходит ref-counting.
Причём move/"полная передача владения" и тут полезена — только move'ается не сама структура, а shared_ptr — это позволяет существенно уменьшить ref-count increment/decrement.
Re[24]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Это же и есть upwards funarg?
Нет, это сначала непонятно на чем основанное связывание prompt finalization проблемы и UFP, констатация простого факта, что их решения сочетаются плохо и выведение отсюда того, UFP не решается. Какой смысл связывать в ФЯ управление памятью в куче, которую эти языки выделяют гигабайтами в секунду и управлением дефицитными ресурсами, требующими закрытия сразу по окончанию использования?
EP>Не так. ФП нужно, но далеко не везде. Писать на C++ только в ФП стиле не имеет смысла.
Я сильно сомневаюсь, что писать на C++ в ФП стиле не то что имеет смысл, а вообще возможно. Если только не трактовать ФП стиль сильно расширительно.
EP>В том примере нужна не ленивость, а monadic код. Ленивость помогла только в одном случае из двух, и как видно в общем случае не позволяет сделать обычный код монадичным.
Ничего не понимаю. Ленивость не делает код монадическим. Она нужна, чтоб функции, полученные как результат комбинации других не делали лишние вычисления/проходы. Т.е. в обсуждаемом случае нужна, чтоб монадический код нормально работал.
И что за один случай из двух?
'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[44]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>И? ) Читаем оттуда:
"The Haskell community uses the term “generic” to describe a form of generative programming with respect to algebraic datatypes (Backhouse et al., 1999; Hinze & Jeuring, 2003; Jeuring & Jansson, 1996). Thus the typical use of the term “generic” with respect to Haskell is somewhat different from our use of the term. However, Haskell does provide support for generic programming as we have defined it here and that is what we present in this section."
А вы точно читали оттуда? С.м. выделенное.
Вообще, то, что называется обобщенным программированием в С++ и других языках, где возможности для него появились в ходе развития языка, в языках где такие возможности были с самого начала вроде хаскеля никак специально не называется. Ну, нет ситуации, когда есть сформированная культура кодирования, и вдруг появляется новая, с использованием параметрического полиморфизма. Потому и дженериками там называются совсем другие вещи.
_>Нормальный там параметрический полиморфизм, просто он времени компиляции.
Параметрический полиморфизм, как и все что касается типов, бывает только времени компиляции. Речь о том, что то, что есть в плюсах не типизируется и потому типов как-то особо и не касается, а больше похоже на кодогенерацию.
_>Если мы посмотрим на язык D (развивающий дальше эту концепцию), то там во время компиляции можно вообще выполнять практически произвольный пользовательский код. И это можно использовать для очень разных интересных возможностей. Например можно ввести полноценный (а не встроенный в язык, как Спирит) DSL, файлы которого будут парситься в процессе компиляции некого D кода и это всё вместе будет превращаться в единый бинарники с полными оптимизациями, включая инлайны. Это несколько другой уровень программирования вообще.
Не понимаю, зачем протягивать в язык метапрограммирование через такую гм... заднюю дверь. Я D не знаю, но судя по увиденным примерам, это именно метапрограммирование, а не код на тайплевеле как в языках с полноценной системой типов т.е. с омегой/завтипами.
_>Кстати, а есть возможно указывать подобное руками или всё управляется только компилятором, а мы влиять не можем (кроме опций компилятора)? Т.е. можем ли мы сказать что например "вот данная сущность должна инлайниться вообще всегда"
Да, можем. Т.е. есть некоторые эвристики, обеспечивающие хороший результат автоматически, есть возможность регулировать эти эвристики (вроде указания своих лимитов на максимальный размер разверток и т.д.), а есть возможность ставить конкретные хинты для компилятора в коде, для крайних случаев, когда автоматика нужного результата не дает, вот это инлайнить, это ни в коем случае не инлайнить, это в интерфейсный файл, это специализировать для такого типа и т.д.
_>Под дженериками естественно Java/C# подразумевались и они заметно слабее варианта C++ именно по части обобщённого программирования.
В основном, да, но это не строгое подмножество возможностей. Они позволяют и такое, что в C++ невозможно.
_>Что касается Хаскеля, то там вообще другая концепция и сравнивать их напрямую глупо.
Почему? Параметрический полиморфизм совершенно точно есть и в хаскеле и в сишарпе и в яве. Просто в хаскеле даже без расширений возможностей больше, вроде абстрагирования от конструктора типа. Сравнивать вполне можно. В статье на которую я ссылку давал продемонстрированы подходы, которые отличаются гораздо сильнее.
_>P.S. Как я понял, ни одного примера заявленных (вами) преимуществ реализации ST/IO Хаскеля над другими языками мы так и не увидим... Несмотря на мои многочисленные напоминания об этом. Ну что же, ладно, так и запишем и закроем эту тему.
Неужели вы уже где-то запостили сравнение и критику "кода в большой монаде" или как там вы это называете и императивного? Думаете, если вы будете бесконечно требовать что-то от меня, полностью игнорируя мои требования я о них забуду? Сразу говорю: этого не произойдет.
'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[22]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
K>>Много ли от "полной передачи владения" пользы в ФП, где коллекции персистентные и типична ситуация, когда на какую-то часть иммутабельной структуры данных куча ссылок от разных ее версий?
_>Хехе, первая часть этой фразы действительно относится к ФП вообще, а вот вторая относится уже всего лишь к одной из конкретных реализаций в конкретном языке.
Да ну? Иммутабельные структуры данных для того и нужны, чтоб иметь несколько версий, разделяющих значительную часть данных, одновременно. Какая же это специфика языка?
'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