Здравствуйте, Ikemefula, Вы писали:
I>Вот это "просто дерево функций" это без пяти минут монада. Что бы это стало полноценной монадо, всё что надо, это дописать две функции и по другому связывать. Все функции будут устроены ровно так же, один к одному.
Да, можно сделать подобную монаду. Но, как видишь, в Спирите это не сделано, т.к. задача эффективно решается и без этого. )
I>Во всех сразу. Пример тебе приводили.
Ну если например взглянуть на C++11, то там future не является монадой. Это всего лишь контейнер с методом get. Так что опять же живём без всяких монад. Вот в будущем, где вроде бы планируют добавить в future функцию then, возможно и получится классическая монада... Но пока ничего такого нет.
Re[57]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Ikemefula, Вы писали:
I>"без монад" в данном случае означает структуру, которую до монады можно допилить ровно двумя функциями. Изначально у тебя "без монад" это просто императивный код.
Хыхы) Ну да, можно допилить этот код до монады. Но!
1. Как видишь он эффективно решает задачу и без этого.
2. Он делает вещи, которые собственно монадам не по силам.
Re[36]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Эммм, это как бы снова не то, что я спрашивал. ))) Ну да ладно. )
Более четкая формулировка вопроса поможет лучше понять его.
_>Я так понимаю, это пожелание иметь "чистые функции" в языке?
Чистые функции. First class effects. Whatever. К проблеме можно подойти по разному и решать по разному в разных языках.
_>Да, в C++ они действительно только административно вводятся. А вот например в языке D они есть (причём за этот факт ничем расплачиваться не надо), но я что-то не видел особых чудес от этого.
Чудес и не надо. А в моем примере они меня спасут от целого класса ошибок.
_>Ну начнём с того, что мы изначально здесь рассматриваем довольно узкий случай, когда "ValidateEvent" нельзя отделить от "ProcessEvent". Т.к. в противном случае мы для этой задачки просто берём какую-нибудь библиотечку конечных автоматов
1. Отделить можно, но очень сложно.
2. Это вовсе не узкий случай, все зависит от бизнес-логики. В конечном итоге мы стремимся записать решение задачи в терминах предметной области наиболее прямолинейным образом. Если это не подразумевает простой возможности отделения валидации от обработки, значит мы должны обойтись без нее.
3. О конечности числа состояний речи не было.
Однако желание свести все к конечному автомату при реализации на С++ понять можно — при простом решении в лоб, приведенном выше, уже накладные расходы на обеспечение иммутабельности State при очень сложном State непрактичны. Как бы я это делал на С++ — ума не приложу, но я и не знаток С++. Кончено, нужны совсем другие техники, но мне сложно представить, что решение может быть проще (короче и очевидней), чем то, что я использую.
_>>А в хаскелле и то и другое не проблема.
_>Пока что не увидел нигде реальных примеров этого.
А чем плох мой пример? хаскелл решает обе мои проблемы совершенно тривиальным образом. (Собственно монада IO — пример такого решения, с единственной разницей что в моем случае я должен аккумулировать эффекты, а не применять их сразу же — но это уже детали реализации моей условной монады Effects, а использование аналогично).
Re[56]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
EP>>Супер! Но опять-таки, это не полноценные монадические вычисления — if(x > y) не сделать, accumulate по массиву Optional!int будет не оптимальным и т.п. Это больше applicative. _>Ага. Я как раз это и демонстрирую, что задача решается эффективно и без монад. _>Насчёт x>y мне кажется, что и не должно автоматом работать с монадой, потому как совершенно непонятно что тут должно происходит в реальном коде. Вот например для optional что там делать? Скорее такой код всё же надо править и как самый простейший вариант — обернуть в лямбду соответствующий кусок кода.
Вот именно — нужно завернуть остаток кода в continuation, тогда всё заработает без изменений.
Представь что у optional есть .then (блокирующий). Тогда задача решается простым await'ом (на stackful coroutine):
operator bool()
{
return await *this;
}
Только для optional есть один момент — если будет Nothing, то продолжение не вызовется, но стэк-то надо раскрутить. Придётся бросить исключение, что убивает всю идею Maybe.
Другой недостаток — продолжение нельзя вызвать несколько раз. Это например используется в List Monad.
В D кстати есть фиберы в std.
_>А что касается accumulate, то это как раз без проблем. Для этого в D надо просто перегрузить ещё один оператор для Optional: opAssign(string op)(r).
Это никак не поможет. Проблема в том, что мы должны остановится как только встретим первое Nothing. Если просто лифтить операторы — то никакого останова не будет (если конечно оптимизатор не додумается), и Nothing'и будут продолжать складываться.
Re[56]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>>>Перегрузить все, чтоб немонадический код вообще без переделок делался монадическим конечно не получится. EP>>Получается что языки с сall-with-current-continuation в этом месте мощнее — можно обычный код сделать монадическим не меняя его. K>Реальный язык или гипотетический?
Реальный — Scheme. "Гипотетический" — Unlambda.
EP>>Да хотя бы: EP>>
EP>>f a = if a == 5 then"foo"else"bar"
EP>>
допустим придёт IO Int. K>Вообще-то if then else синаксис перегружаемый. На практике, правда, перегружать его никто не будет,
Ок, не знал.
K>да и вообще уместность его (if-a) применения в хаскеле достаточно спорная. Там скорее какой-нибудь комбинатор по случаю используют, который лифтится. K>Более серьезна проблема с ПМ, но и паттерн-матчинг — инструмент очень низкоуровневый, который лучше запереть в каком-нибудь комбинаторе.
Это всё понятно — но это другой вопрос.
EP>>Я думаю можно сделать пример монады, где это не сработает (например добавить счётчик во внутрь монады, но нужно проверить законы моноида). K>Если монада в принципе раннее завершение не поддерживает — конечно это работать не будет. Но весь смысл Maybe, в общем-то, в раннем завершении. Зачем она иначе нужна-то?
Я имел ввиду делать раннее завершение и считать сколько раз пытались вытащить Nothing. Монадический fold остановится, а обычный должен будет пройтись по всем значениям.
EP>>А как именно это реализовано (мемоизация, etc?) и есть ли гарантия этой оптимизации? K>Это называется нормальный порядок редукции. Вовсе никакая не оптимизация. Я скопировал результаты из интерпретатора, а там оптимизаций нет.
Как порядок вычислений здесь поможет?
Вот есть:
((a+b)+c)
^^^ результат Nothing
второй лифтанутый (+) не должен быть вызван.
Как именно это реализуется внутри, и есть ли гарантия?
Re[48]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
I>>Во всех сразу. Пример тебе приводили. _>Ну если например взглянуть на C++11, то там future не является монадой. Это всего лишь контейнер с методом get. Так что опять же живём без всяких монад. Вот в будущем, где вроде бы планируют добавить в future функцию then, возможно и получится классическая монада... Но пока ничего такого нет.
Кстати, в Boost.Thread у future есть .then — я его использовал для реализации await (хотя там не обязательно привязываться к конкретному типу future, можно сделать customiztion point).
Re[52]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Конечно. Только вот в современных мультипарадигменных языках это всё (и ещё очень много чего) уже есть, причём как раз с полной магией. Т.е. мой вопрос насчёт полезности монад в таких языках по прежнему остаётся в силе...
А, я понял, простите за тупизну.
Фактически вы противопоставляете встроенное расширяемому. То есть, что лучше — встраивать популярные монады прямо в язык, или научить язык работать с любыми монадами, а потом реализовывать плюшки библиотеками.
Ответ зависит от нетехнических факторов. Нужно понимать, что во времена юности С++ вопрос "чем в современных языках расчётов (читай 'Фортран') могут быть полезны ваши Объекты. Тип complex у нас уже есть, и как раз с полной магией" был вполне себе в силе. То есть, Бъярни выбрал расширяемость вместо встроенности.
А вот Хейльсберг в С# всегда занимал консервативную позицию: "мы улучшаем не фичи, а сценарии". То есть, если у пользователей языка есть конкретная проблема, то команда выбирает такой дизайн, который хорошо решает именно эту задачу, а не универсальный ответ на множество несвязанных задач. Характерный пример — оператор infoof, который должен был возвращать метаданные по мемберу (аналог typeof(typename)), только для методов, свойств, событий, и полей). Типичные сценарии, который хотелось решить с его помощью — реализация INotifyPropertyChanged так, чтобы гарантировать корректное имя свойства в аргументах, и логгирование вызовов, чтобы гарантировать корректное имя текущего метода или свойства. В итоге вместо всемогущего инфуфа (реализация которого создаёт больше проблем, чем решает), прикрутили атрибут CallerMemberName, применяемый к параметрам.
Моё мнение такое, что должны быть и те и другие языки. В том смысле, что для обкатки полезных идей нужен язык, в котором можно добавить более-менее всё, что угодно; а уже потом зарекомендовавшие себя идеи можно переносить в мейнстрим, где важнее обеспечить плавную кривую обучения и обложить всё внятными сообщениями компилятора.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[50]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Ну если мы хотим реализовать всё одной функцией, то соответственно у нас должен быть универсальный алгоритм, оптимальный для любых типов. Если же для каких-то типов более оптимален другой алгоритм функции, то для этого в C++ есть явная специализация шаблонов.
Не, мы хотим одним махом обработать все функции вида power, определённые для типа T, залифтив их к типу anything<T>. _>Ну это вообще другая область. Скорее про лямбды, т.е. типа тех игр, которыми занимается Boost.Phoenix .
Ну там от монад только SelectMany. Тем не менее, он есть, а его конкретная семантика определяется стандартным алгоритмом выбора перегрузки.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[52]: Есть ли вещи, которые вы прницпиально не понимаете...
то будет работать для любой полугруппы.
Не будет, т.к. убрав код, вы внесёте ошибку.
EP>Нужна всего лишь ассоциативная операция (например умножение матриц или конкатенация строк).
EP>>>
EP>>>Power is generalized exponentiation: it raises the value x to the power n, where n is a non-negative integer.
S>>Важное выделено. Даже в банальную вещественную степень возвести эта штука не сможет. EP>Для вещественной степени будет больше требований к параметру.
Вы, по-моему, не понимаете вопроса. Вы как собираетесь возводить, скажем, число 1.5 в степень -i*Pi, при помощи этой функции?
Или хотя бы в степень 0.5?
EP>А к чему вообще вопрос про power?
К тому, что рассчитывать на то, что power волшебным образом заработает с optional<double>, не стоит. То есть работа должна быть сделана на стороне optional, а не на стороне power.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[52]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали: _>Да, и кстати... Можно прикручивать подобные вещи и без compiler magic и без монад, используя совсем другие возможности языка. Причём будет получаться гораздо эффективнее явных монад. Вот http://www.rsdn.ru/forum/philosophy/5427522
пример подобного.
Если я правильно понял, то "без монад" ничего не получится, т.к. не всякую функцию можно использовать в качестве bind.
То есть вы предлагаете использовать монады, не используя термин "монады".
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[48]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
I>>Вот это "просто дерево функций" это без пяти минут монада. Что бы это стало полноценной монадо, всё что надо, это дописать две функции и по другому связывать. Все функции будут устроены ровно так же, один к одному.
_>Да, можно сделать подобную монаду. Но, как видишь, в Спирите это не сделано, т.к. задача эффективно решается и без этого. )
Это не важно, главное что монада и такой вот функтор в языке без поддержки монад есть практически одно и то же. Более того, такой метод есть уже в спирите, даже целых несколько, например binary_operator::eval или unary_operator::eval
Собственно как то иначе сделать спирит будет затруднительно, парсер-комбинаторы сами по себе монадические до безобразия. Парсеры комбинаторы без монад всё равно что промисы без метода then
I>>Во всех сразу. Пример тебе приводили.
_>Ну если например взглянуть на C++11, то там future не является монадой. Это всего лишь контейнер с методом get. Так что опять же живём без всяких монад. Вот в будущем, где вроде бы планируют добавить в future функцию then, возможно и получится классическая монада... Но пока ничего такого нет.
Промисы у которых всегда есть метод then не в счёт ?
Re[58]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>1. Как видишь он эффективно решает задачу и без этого. _>2. Он делает вещи, которые собственно монадам не по силам.
Это чтото очень интересное и я шота не пойму, какие вещи не под силу монадам ? В твоём любимом С++ все до одной фичи которые тебе нравятся, это монады встроеные в язык. И этим монадам, оказывается, чтото не под силу
Re[49]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
_>>Ну если например взглянуть на C++11, то там future не является монадой. Это всего лишь контейнер с методом get. Так что опять же живём без всяких монад. Вот в будущем, где вроде бы планируют добавить в future функцию then, возможно и получится классическая монада... Но пока ничего такого нет.
EP>Кстати, в Boost.Thread у future есть .then — я его использовал для реализации await (хотя там не обязательно привязываться к конкретному типу future, можно сделать customiztion point).
Шота сиплюсники путаются в показаниях Если then есть у future, то это уже гарантировано монанднее некуда.
Re[57]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>>Получается что языки с сall-with-current-continuation в этом месте мощнее — можно обычный код сделать монадическим не меняя его. EP>Реальный — Scheme. "Гипотетический" — Unlambda.
И вы можете такую "монадизацию" существующего кода без его изменения проиллюстрировать примером?
EP>Я имел ввиду делать раннее завершение и считать сколько раз пытались вытащить Nothing. Монадический fold остановится, а обычный должен будет пройтись по всем значениям.
Ничего не понял. Что за обычный и монадический фолды? Почему один остановится, а другой нет?
EP>Как порядок вычислений здесь поможет? EP>Вот есть: EP>
EP>((a+b)+c)
EP> ^^^ результат Nothing
EP>
второй лифтанутый (+) не должен быть вызван. EP>Как именно это реализуется внутри, и есть ли гарантия?
Скобки у вас не там. Свертка-то правая.
foldr (+) 0 [a..z]
= {foldr (+) 0 (a:as) = a + foldr (+) 0 as}
a + (b + (c ... + 0))
= {(+) = liftM2 (+)}
liftM2 (+) a (b + (c ... + 0))
= {liftM2 f a b = a >>= \a -> b >>= \b -> return $ f a b}
a >>= \a -> (b + (c ... + 0)) >>= \b -> a + b
= {a = Nothing} -- и вот у нас попадается NothingNothing >>= \a -> (b + (c ... + 0)) >>= \b -> a + b
= {Nothing >>= _ = Nothing} -- вот здесь, например, нормальный порядок
-- позволяет не считать все остальное. При аппликативном порядке все, что
-- передается в функцию, вычисляется перед ее вызовом, так что вычислять
-- придется до посинения.Nothing
'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[40]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
K>>Что в данном случае понимается под "обобщенное программирование"? Кодогенерация? _>В C++ это шаблоны, в Java generics и т.п..
Тогда непонятно, что означают слова "параметрический полиморфизм же может иметь разные реализация, причём даже в рамках одного языка. Самая эффективная естественно через обобщённое программирование". Параметрический полиморфизм, получается, один из инструментов обобщенного программирования (наряду с мл-ными функторами и т.д.), что означает его реализация через обобщенное программирование тогда.
_>Естественно речь про диспетчеризацию, т.к. именно она определяет эффективность (в смысле быстродействия).
На быстродействие влияет и то и другое. Диспетчеризация в рантайме не позволяет заинлайнить функцию из словаря, например, (ну, если только у нас не какой-нибудь трассирующий JIT), а параметрический код в одной версии для указателей заставляет все боксить.
_>Я правильно понимаю, что нормальная оптимизация происходит только при условии, что обобщённый код и использующего его код находятся в одной единице компиляции?
Нет. Одна единица компиляции (модуль, например) компилируется в объектный файл и интерфейсный файл, содержащий развертки (код из модуля в той или иной степени прекомпиляции, сериализованное аст, например — ну, не важно). Никаких разверток в файле может вообще не быть — никаких межмодульных оптимизаций тогда не будет, компиляция полностью раздельная. Допустим, что компилировали мы с -O0, для ускорения компиляции во время разработки. Теперь мы хотим получить итоговую версию, и компилируем с -O2 в интерфейсных файлах теперь гораздо больше разверток, а компилятор не стесняется их использовать, когда компилирует зависимые модули. Разумеется, компиляция теперь не полностью раздельная, требует больше времени, но зато есть инлайн и специализация. Противоположная крайность такого подхода, когда весь код всех компилируемых модулей программы оказывается в развертках в интерфейсных файлах — теперь никакой раздельной компиляции нет, есть полнопрограммный анализ, естественно, чудовищно медленный, зато дающий на выходе самый быстрый код.
_>Если да, то получается что никаких отличий от C++ нет — те же самые два варианта: нормальная оптимизация/совместная компиляция и плохая оптимизация/раздельная компиляция.
Из описанного выше, думаю, понятно, что вариантов не два а сколько угодно.
K>>Смысл в том, чтоб обнаруживать ошибки до того, как какой-то конкретный int вообще откуда-то появится. _>Ну так это тогда уже не к концептам и т.п., а к вообще другому языку, даже не знаю какому. Т.к. в языках с шаблонной техникой, шаблоны не трогаются пока они не используются.
Ну да, "шаблоны не трогаются" а дженерики, параметризуемые модули и т.д. наоборот, проверяются и типизируются. В известно каких языках — всех, кроме C++ и его клонов.
_>Лично я считаю, что из всех существующих концепций в программирование, метапрограммирование — это наиболее сильный инструмент с любых точек зрения. И развивать в языке надо прежде всего его. Например C++ хорошо бы подтянуть хотя бы до уровня языка D.
Я согласен, что метапрограммирование надо развивать, речь не про развитие, а про путаницу, развитию не способствующую.
_>Ага, но саму разницу внутри Хаскеля тоже прекрасно ощущаете...
Ну, это было бы довольно странно, если бы код, который что-то делал, выглядел так же просто, как код, который этого не делает — это означает только, что есть очевидные пути для улучшения этого не делающего того же кода.
_>Касательно сравнения монадного кода Хаскеля и кода на современных императивных языках. На мой взгляд тут довольно просто провести сравнение. Ведь если не вводить кучу уровней абстракции (а всё равно нижний уровень придётся писать), то подобный код сводится к последовательности вызовов некого системного API. Так вот на императивных языках это прямо буквально так и записывается. Ну а на монадах Хаскеля это записывается аналогично, плюс ещё некий набор ритуальных плясок... Разве не очевидно, какой вариант лучше? )
Непонятно, почему это нужно рассматривать случай, в котором куча абстракций не вводится? Тогда в языках вроде хаскеля вообще смысла нет, потому что в них все оптимизировано именно для облегчения построения таких абстракций. Именно для этого существуют, в числе прочего, и то, что вы называете "ритуальными плясками". Если же слои абстракций возводить не нужно, то и языки программирования, кроме самых примитивных не нужны, а различия между ними не существенны. Нужны и существенны только прилежание и усидчивость.
'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[53]: Есть ли вещи, которые вы прницпиально не понимаете...
то будет работать для любой полугруппы. S>Не будет, т.к. убрав код, вы внесёте ошибку.
Это будет не ошибка, а precondtition: n > 0
EP>>Нужна всего лишь ассоциативная операция (например умножение матриц или конкатенация строк). EP>>>>
EP>>>>Power is generalized exponentiation: it raises the value x to the power n, where n is a non-negative integer.
S>>>Важное выделено. Даже в банальную вещественную степень возвести эта штука не сможет. EP>>Для вещественной степени будет больше требований к параметру. S>Вы, по-моему, не понимаете вопроса. Вы как собираетесь возводить, скажем, число 1.5 в степень -i*Pi, при помощи этой функции? S>Или хотя бы в степень 0.5?
С помощью этой — естественно никак, она слишком общая. У неё мало требований к аргументу, поэтому и степень натуральная.
Если будет обобщённая функция для возведения в вещественную степень, или в комплексную — то и требований будет больше.
EP>>А к чему вообще вопрос про power? S>К тому, что рассчитывать на то, что power волшебным образом заработает с optional<double>, не стоит. То есть работа должна быть сделана на стороне optional, а не на стороне power.
А, то есть вопрос про liftA2? Выше уже был пример лифтинга в C++.
Re[50]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Ikemefula, Вы писали:
EP>>Кстати, в Boost.Thread у future есть .then — я его использовал для реализации await (хотя там не обязательно привязываться к конкретному типу future, можно сделать customiztion point). I>Шота сиплюсники путаются в показаниях Если then есть у future, то это уже гарантировано монанднее некуда.
В C++11 std::future нету .then, там есть только блокирующий get. Есть boost::future, к которому добавили .then (afaik, после выхода C++11).
Re[51]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>>>Кстати, в Boost.Thread у future есть .then — я его использовал для реализации await (хотя там не обязательно привязываться к конкретному типу future, можно сделать customiztion point). I>>Шота сиплюсники путаются в показаниях Если then есть у future, то это уже гарантировано монанднее некуда.
EP>В C++11 std::future нету .then, там есть только блокирующий get. Есть boost::future, к которому добавили .then (afaik, после выхода C++11).
И что это меняет ?
Re[18]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали: K>>Да, собственно, всего. Там даже Upward Funarg Problem не решена. EP>Всё решено.
Смелое заявление. А подтверждение этому какое-нибудь есть?
Скрытый текст
Это шутка, на самом деле, никакого практичного решения этой проблемы кроме точного, уплотняющего кучу ГЦ с поколениями пока не придумали, а такого в C++ никогда не будет по понятным причинам. Так что просил подтверждение я, разумеется, не всерьез.
EP>Сборщик мусора пытается управлять только памятью, да и то возможны утечки. Если же брать управление ресурсами в общем — то и C# и Haskell сливают C++. EP>Например попробуй сделать аналог: EP>
EP>auto foo()
EP>{
EP> auto x = 5;
EP> ofstream f("log.txt");
EP> auto y = make_shared<Foo>(11);
EP> return [f=move(f), =](auto z)
EP> {
EP> f << z << endl;
EP> return x + y->data;
EP> };
EP>}
EP>int main()
EP>{
EP> cout << foo()("OMG!");
EP>}
EP>
В языках с ГЦ для детерминированного освобождения используются всякие брекеты вроде using
let foo () =
let x = 5
use file = new Movable<_>(System.IO.File.CreateText(@"d:\log.txt"))
(*failwith "BANG!" //тут ресурс будет освобожден детерминированно *)let y = 11
let file = file.Move()
(* failwith "BANG!" //а тут сборщиком мусора *)fun s -> use f = file
(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
f.Write(s + "!")
x + y
printfn "%d" <| foo () "OMG"
EP> Как только замыкание выходит из scope файл закрывается и Foo освобождается.
Звучит довольно бессмысленно. Во первых, тут владение открытым файлом передается в функцию, которую вы называете "замыканием", хотя замыкание это сомнительное. В общем-то смысл замыкания как раз в продлении жизни скоупа из которого оно уходит. Так что замыкание и решение UFP — это работающий [&], а не копирование. При сколько нибудь существенном использовании ФП вы устанете ждать, пока там что-то копируется, да и смысл замыкания часто именно в том, чтоб разные функции ушедшие из одного скоупа впоследствии работали друг с другом через замыкание на него. Т.е. в большинстве практически полезных случаев придется использовать тормозные и замусоривающие код умные указатели со счетчиками. Не говоря уж о том, что в ФП циклические ссылки в порядке вещей. Ну так вот, в эту функцию владение передается и файл закрывается после того, как эта функция отработала. Все верно?
Вообще говоря, тут утечка ресурса. Никакого практического смысла возвращать функцию и тут же ее использовать нет. На практике функция будет болтаться в памяти как часть какого-то замыкания или структуры данных, хранящих функции и возможно вовсе никогда не будет вызвана. Т.е. момент ее выполнения будет определен динамически, ни о каком детерминированном высвобождении ресурса тут речи не идет. Поэтому, за захват ресурса в замыкание и отправление этого замыкания в свободное плавание неплохо бы бить канделябром. Более-менее осмысленное решение, это делать функцию вида ЭтоНамНадоЧтобПолучитьРесурс -> (ОткрытыйРесурс -> Результат) -> Результат, которая сначала собирает через второй аргумент функции, которые что-то будут с ресурсом делать, а потом выполняется сама, открывая ресурс, скармливая всем этим функциям и закрывая его. Т.е. те самые брекеты и их развитие вроде pipes-safe и resourcet.
'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[19]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>>>Да, собственно, всего. Там даже Upward Funarg Problem не решена. EP>>Всё решено. K>Смелое заявление. А подтверждение этому какое-нибудь есть? (cut) Это шутка, на самом деле, никакого практичного решения этой проблемы кроме точного, уплотняющего кучу ГЦ с поколениями пока не придумали, а такого в C++ никогда не будет по понятным причинам. Так что просил подтверждение я, разумеется, не всерьез.(/cut)
C++ предоставляет выбор — можно сделать move, можно copy, можно ref-counted, а можно вообще зарегистрировать deleter в scope повыше. Причём это работает для любых ресурсов, а не только для памяти как в языках с GC.
EP>>Сборщик мусора пытается управлять только памятью, да и то возможны утечки. Если же брать управление ресурсами в общем — то и C# и Haskell сливают C++. EP>>Например попробуй сделать аналог: K>В языках с ГЦ для детерминированного освобождения используются всякие брекеты вроде using
Ну вот в C# есть GC, и using'и:
using System;
using System.IO;
public class Test
{
public delegate void fireworks();
public static fireworks make_closure(FileStream fs)
{
return () => fs.Read(new byte[10], 0, 10); // "чемодан без ручки"
}
public static fireworks fire()
{
using (var fs = File.Open("file", FileMode.Open))
{
return make_closure(fs);
}
}
public static void Main()
{
fire()(); // System.ObjectDisposedException
}
}
Тут-то куда писать using?
K>
K>let foo () =
K> let x = 5
K> use file = new Movable<_>(System.IO.File.CreateText(@"d:\log.txt"))
K> (*failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
K> let y = 11
K> let file = file.Move()
K> (* failwith "BANG!" //а тут сборщиком мусора *)
K> fun s -> use f = file
K> (* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
K> f.Write(s + "!")
K> x + y
K>printfn "%d" <| foo () "OMG"
K>
Можно ли вызвать замыкание несколько раз? Когда конкретно происходит удаление?
EP>> Как только замыкание выходит из scope файл закрывается и Foo освобождается. K>Звучит довольно бессмысленно. Во первых, тут владение открытым файлом передается в функцию, которую вы называете "замыканием", хотя замыкание это сомнительное.
[f=move(f), =](auto z) mutable
{
f << z << endl;
return x + y->data;
};
Ну а что это тогда если не замыкание?
K>В общем-то смысл замыкания как раз в продлении жизни скоупа из которого оно уходит. Так что замыкание и решение UFP — это работающий [&], а не копирование.
Работающего [&] при передачи вверх естественно не будет. Но говорить что работающий [&] это необходимое условие для решения UFP — неправильно. Вот например цитата из wiki:
Another solution is to simply copy the value of the variables into the closure at the time the closure is created.
В разных языках разные подходы
K>При сколько нибудь существенном использовании ФП вы устанете ждать, пока там что-то копируется,
Копировать необязательно, часто достаточно move.
K>да и смысл замыкания часто именно в том, чтоб разные функции ушедшие из одного скоупа впоследствии работали друг с другом через замыкание на него. Т.е. в большинстве практически полезных случаев придется использовать тормозные и замусоривающие код умные указатели со счетчиками.
Во-первых в большинстве полезных случаев ref-counted не нужен, а во-вторых код не замусоривается:
K>Не говоря уж о том, что в ФП циклические ссылки в порядке вещей.
Они действительно возможны, но разве это прям в порядке вещей? Особенно учитывая иммутабельность.
K>Ну так вот, в эту функцию владение передается и файл закрывается после того, как эта функция отработала. Все верно?
Нет. В эту лямбду захватывается файл, а он закрывается в деструкторе лямбды. Саму лямбду можно вызывать много раз.
K>Вообще говоря, тут утечка ресурса.
Где?
K>Никакого практического смысла возвращать функцию и тут же ее использовать нет.
Это только иллюстрация проблемы UFP, естественно в большинстве use-case'ов это замыкание будет вызываться несколько раз, и возможно будет передаваться ещё выше. Например:
{
auto log = make_log("...");
log(1);
log("1");
log(foo);
} // log destructor cleans up its resources
Как сделать подобное в C# или Haskell?
K>На практике функция будет болтаться в памяти как часть какого-то замыкания или структуры данных, хранящих функции и возможно вовсе никогда не будет вызвана. Т.е. момент ее выполнения будет определен динамически, ни о каком детерминированном высвобождении ресурса тут речи не идет.
Выше use-case.
K>Поэтому, за захват ресурса в замыкание и отправление этого замыкания в свободное плавание неплохо бы бить канделябром.
В языках где есть готовые решения только для памяти, но нет ничего для остальных ресурсов — действительно, такой номер не пройдёт. Потому что GC для памяти это полумера, и не решает UFP в общем случае
K>Более-менее осмысленное решение, это делать функцию вида ЭтоНамНадоЧтобПолучитьРесурс -> (ОткрытыйРесурс -> Результат) -> Результат, которая сначала собирает через второй аргумент функции, которые что-то будут с ресурсом делать, а потом выполняется сама, открывая ресурс, скармливая всем этим функциям и закрывая его. Т.е. те самые брекеты и их развитие вроде pipes-safe и resourcet.
Часто открытие/закрытие ресурсов может быть накладным, не говоря о том что может быть вообще недопустимо (например квота на сессии).