Здравствуйте, Ikemefula, Вы писали:
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). I>И что это меняет ?
Вы обсуждали std::future — сейчас там нет .then, так что никакой "путаницы в показаниях" нет.
std не единственная библиотека где есть future/promise, и в некоторых из них есть .then — например PPL, Boost.
Re[37]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, meadow_meal, Вы писали:
_>Более четкая формулировка вопроса поможет лучше понять его.
Вопрос был о преимущества техники контроля эффектов Хаскеля. Соответственно ответом на него должен был бы быть код на Хаскеле с пояснением почему аналогичное трудно реализовать на других языка программирования.
_>Чудес и не надо. А в моем примере они меня спасут от целого класса ошибок.
Я просто заметил, что хотя в D оно есть, но им не особо пользуются. ) Т.е. конечно же у всех соответствующих функций в стандартной библиотеке проставлен модификатор pure и т.п. Но я вот что-то не помню каких-то мест, где бы принимались pure функции и не принимались обычные. Понятно о чём я? )
А так да, для определённого автоматического самоконтроля это конечно полезная вещь.
_>Однако желание свести все к конечному автомату при реализации на С++ понять можно — при простом решении в лоб, приведенном выше, уже накладные расходы на обеспечение иммутабельности State при очень сложном State непрактичны. Как бы я это делал на С++ — ума не приложу, но я и не знаток С++. Кончено, нужны совсем другие техники, но мне сложно представить, что решение может быть проще (короче и очевидней), чем то, что я использую.
На самом деле, если приглядеться к вашему примеру, то можно увидеть, что требование неразделимости validateEvent и processEvent не выполняется и у вас. Т.е. оно более сильное, чем на самом деле в вашем коде. А реально у вас там работает требование "неразделённость validateEvent и analyzeEvent", в то время как реальный action (требующий результаты analyzeEvent) вполне отделён. Т.е. на самом деле опять же получился классический конечный автомат. Просто его action и guard теперь будут использовать некую общую переменную effect, а код самого автомата (вида "state1+event[guard]/action==state2") нисколько при этом не меняется. Конечно это не совсем приятно с точки зрения красоты архитектуры, но раз у нас такой дико сложный guard, что его неэффективно повторять внутри action, то подобная оптимизация быстродействия является вполне разумным решением.
_>А чем плох мой пример? хаскелл решает обе мои проблемы совершенно тривиальным образом. (Собственно монада IO — пример такого решения, с единственной разницей что в моем случае я должен аккумулировать эффекты, а не применять их сразу же — но это уже детали реализации моей условной монады Effects, а использование аналогично).
Так а где само решение на Хаскеле то? ) Я пока увидел только саму проблему и вроде как её "плохое" решение на псевдокоде, которое реализуется в любом языке. А где хорошее решение на Хаскеле?
Re[57]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Вот именно — нужно завернуть остаток кода в continuation, тогда всё заработает без изменений. EP>Представь что у optional есть .then (блокирующий). Тогда задача решается простым await'ом (на stackful coroutine): EP>
Только для optional есть один момент — если будет Nothing, то продолжение не вызовется, но стэк-то надо раскрутить. Придётся бросить исключение, что убивает всю идею Maybe. EP>Другой недостаток — продолжение нельзя вызвать несколько раз. Это например используется в List Monad. EP>В D кстати есть фиберы в std.
В итоге получается, что вместо того, что бы устраивать монадопобные игры, проще всего использовать boost.optional в его оригинальном виде. А именно, заменяем Т на optional<T> и расставляем везде по коду звёздочки. В итоге всё будет работать как и раньше, а в случае nothing прилетит исключение. Конечно это не получится полное отсутствие модификации кода, но очень близкое к этому.
EP>Это никак не поможет. Проблема в том, что мы должны остановится как только встретим первое Nothing. Если просто лифтить операторы — то никакого останова не будет (если конечно оптимизатор не додумается), и Nothing'и будут продолжать складываться.
Ааа, в этом смысле, действительно. Я даже не подумал о таком варианте в начале. У меня nothing почему-то ассоциировалось с чем-то вроде исключительной ситуации, которую оптимизировать не надо. )
Re[58]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали: EP>>>>Получается что языки с сall-with-current-continuation в этом месте мощнее — можно обычный код сделать монадическим не меняя его. EP>>Реальный — Scheme. "Гипотетический" — Unlambda. K>И вы можете такую "монадизацию" существующего кода без его изменения проиллюстрировать примером?
Да, практически.
Единственное изменение — это вызов get вместо простого использования значения напрямую, но это мелочи (в C++/D/Haskell этот get был бы не нужен. может быть и в Scheme это тоже как-то возможно).
Пользовательская функция:
(define (user_func get Mx My)
(define x '())
(define y '())
(set! x (get Mx))
(if (less x 1000)
(begin
(set! y (get My))
(* x y))
'()))
как видно — она достаточно императивная, никакого нарезания control flow на продолжения как в Haskell нет.
Запускаем разными способами:
EP>>Я имел ввиду делать раннее завершение и считать сколько раз пытались вытащить Nothing. Монадический fold остановится, а обычный должен будет пройтись по всем значениям. K>Ничего не понял. Что за обычный и монадический фолды? Почему один остановится, а другой нет?
foldl остановится? Возможно ли написать foldlm, с тем же порядком вычислений, но так чтобы он остановился? K>Скобки у вас не там. Свертка-то правая. K>
K>foldr (+) 0 [a..z]
K>= {foldr (+) 0 (a:as) = a + foldr (+) 0 as}
K>a + (b + (c ... + 0))
K>= {(+) = liftM2 (+)}
K>liftM2 (+) a (b + (c ... + 0))
K>= {liftM2 f a b = a >>= \a -> b >>= \b -> return $ f a b}
K>a >>= \a -> (b + (c ... + 0)) >>= \b -> a + b
K>= {a = Nothing} -- и вот у нас попадается Nothing
K>Nothing >>= \a -> (b + (c ... + 0)) >>= \b -> a + b
K>= {Nothing >>= _ = Nothing} -- вот здесь, например, нормальный порядок
K>-- позволяет не считать все остальное. При аппликативном порядке все, что
K>-- передается в функцию, вычисляется перед ее вызовом, так что вычислять
K>-- придется до посинения.
K>Nothing
K>
Теперь дошло, спасибо. Остановка происходит за счёт гарантированной ленивости, верно?
Re[58]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>В итоге получается, что вместо того, что бы устраивать монадопобные игры, проще всего использовать boost.optional в его оригинальном виде. А именно, заменяем Т на optional<T> и расставляем везде по коду звёздочки. В итоге всё будет работать как и раньше, а в случае nothing прилетит исключение.
Maybe — это фактически и есть исключение, но без значения. Точнее только с одним значением:
struct Nothing {};
...
throw Nothing{};
(в Haskell даже есть монада Exception, которая очень похожа на Maybe)
Поэтому, естественно, в случае C++/D лучше воспользоваться встроенными исключениями в язык (причём оптимизированными).
_>Конечно это не получится полное отсутствие модификации кода, но очень близкое к этому.
Звёздочки, кстати, расставлять в пользовательском коде не обязательно, достаточно расставить внутри лифтанутых операторов.
Re[53]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Sinclair, Вы писали:
S>А, я понял, простите за тупизну. S>Фактически вы противопоставляете встроенное расширяемому. То есть, что лучше — встраивать популярные монады прямо в язык, или научить язык работать с любыми монадами, а потом реализовывать плюшки библиотеками.
Не совсем такая логика. Сейчас попробую по пунктам изложить:
1. Я пока что-то сами языки программирования не проектирую, поэтому пока подхожу к ним исключительно с точки зрения потребителя. Т.е. основной является проблема выбора оптимального, на продумывание идеальной архитектуры нового.
2. Выбор языка естественно завязан на задачи и про "монадность" при этом думается в последнюю очередь. Т.е. для начала надо чтобы язык хотя бы позволял создавать необходимый код, не важно как будет выглядит его исходник. А уже после этого, из оставшегося множества выбираем вариант с наиболее эффективным синтаксисом.
3. ОК, выбрали язык. В нашем случае это оказался C++. И вот теперь посмотрим на него с точки зрения монад. C++, благодаря своим шаблонам, позволяет создавать и использовать явные монады ничуть не слабее Хаскеля (а вот C# например не может так). Казалось бы вот сейчас наделаем этих самых монад как и в Хаскеле. Но мы смотрим на язык и видим, что практически для всех целей, которые решаются в Хаскеле известными монадами, в C++ есть свои, уже вложенные в язык средства. Причём они точно не являются явными монадами (никаких явных функций bind и т.п.), а являются просто чем-то отдалённо похожим, но при этом работают ещё более эффективно, чем явные монады, т.к. реализуются компилятором.
В результат всего это возникает логичный вопрос: есть ли какие-то задачи, которые отлично решаются с помощью монад и при этом не имеют более эффективного решения на базе встроенных в C++ средств? Потому как если есть, то мы тут же быстренько накидаем соответствующую явную монаду и будем её постоянно использовать в этих задачах.
Собственно этот вопрос я и пытался выяснить в дискуссии в данной темке. Однако пока не увидел ни одного подходящего примера.
Кстати, возможно кому-то показалось что я настроен "против монад". Это естественно не так, ведь я могу спокойно использовать их в своём языке, так что никаких ммм предубеждений такого типа у меня быть не может. Просто для того, чтобы решить их использовать, надо убедиться, что они могут давать результат лучше встроенных средства, а пока что ни один пример тут не выдержал подобной проверки. Т.е. действительно в каком-то смысле у меня получается вывод "монады не нужны". ))) Но не из-за каких-то базовых принципов, а только потому, что пока так и не видел ни единого подходящего примера обратному.
S>Моё мнение такое, что должны быть и те и другие языки. В том смысле, что для обкатки полезных идей нужен язык, в котором можно добавить более-менее всё, что угодно; а уже потом зарекомендовавшие себя идеи можно переносить в мейнстрим, где важнее обеспечить плавную кривую обучения и обложить всё внятными сообщениями компилятора.
Согласен. ) Только я бы оставил как раз расширяемый язык для мейнстрима (возможно если и не по головам считать, то по лидирующим продуктам), а простой и безопасный предназначил для толп малоквалифицированных программистов, разрабатывающих корпоративный софт в не IT компаниях.
Re[54]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>В результат всего это возникает логичный вопрос: есть ли какие-то задачи, которые отлично решаются с помощью монад и при этом не имеют более эффективного решения на базе встроенных в C++ средств? Потому как если есть, то мы тут же быстренько накидаем соответствующую явную монаду и будем её постоянно использовать в этих задачах. _>Собственно этот вопрос я и пытался выяснить в дискуссии в данной темке. Однако пока не увидел ни одного подходящего примера.
Вот тут есть список "интересных" монад. Каких-то недостающих в C++ монад я не увидел.
Я думаю большим преимуществом монад является явный контроль над эффектами. Если какой-то код выпустит наружу исключение — это будет видно по его типу. Если кто-то полезет в I/O — то без явного разрешения (либо чего-то типа unsafePerformIO, или ForeignFunctionInterface) ничего не получится. То есть получается чуть больше гарантий по умолчанию.
Re[51]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Sinclair, Вы писали:
S>Не, мы хотим одним махом обработать все функции вида power, определённые для типа T, залифтив их к типу anything<T>.
Ааа, это... Ну так это же тут обсуждалось уже.
1. В D мы делаем это одной функцией на каждый тип монад (optional, list и т.п.). В том смысле, что на все функции в природе вообще, а не только на power.
2. В C++ такого нет, и мы вынуждены будем написать по одному дополнительному шаблону на каждый набор функций с одним именем (например для всех power). Зато этот шаблон для power можно написать для произвольного типа монады, а не только optional.
3. В C# не получится даже вариант C++ и придётся выписывать вариант для каждого имени функции и для каждого типа монады.
4. В Хаскеле. Я в нём не особый специалист (стёр его уже давно у себя, как не понравившийся), но насколько помню, там ситуация полностью аналогичная C++.
Это именно расклад для ситуации, когда мы не хотим вообще менять существующий код, который использует нашу функцию power.
Re[53]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, alex_public, Вы писали: _>>Да, и кстати... Можно прикручивать подобные вещи и без compiler magic и без монад, используя совсем другие возможности языка. Причём будет получаться гораздо эффективнее явных монад. Вот http://www.rsdn.ru/forum/philosophy/5427522
пример подобного. S>Если я правильно понял, то "без монад" ничего не получится, т.к. не всякую функцию можно использовать в качестве bind. S>То есть вы предлагаете использовать монады, не используя термин "монады".
Какой bind? Где он там? ) Там даже намёков нет на подобное и вообще другого уровня код. Мы там просто указали, что у optional есть функция-член вида "auto ??? () {???(this.value);}", где ??? подставит компилятор. Т.е. какую бы функцию член мы не затребовали от optional, он нам её предоставит, реализовав её через вызов одноимённой функции на внутреннем значение.
Так что монад тут и близко нет. Отдалённо можно обозвать это функтором, но и то только по аналогии, т.к. напрямую никто тут функции в optional не передаёт.
Re[49]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Ikemefula, Вы писали:
I>Это не важно, главное что монада и такой вот функтор в языке без поддержки монад есть практически одно и то же.
А что за таинственная поддержка монад языком? ) Уже который раз от тебя слышу, но так что-то и не понял.
И между функтором и монадой есть большая разница в любом языке. Мы можем сделать из монады функтор (не меняя код!), но не можем сделать из функтора монаду. Во всяком случае без введения ещё одной особой операции (т.е. опять же правки кода).
I>Более того, такой метод есть уже в спирите, даже целых несколько, например binary_operator::eval или unary_operator::eval
В случае монады или даже функтора, у нас имеется некий контейнерный тип, в который запаковываются некие внутренние типы. Вот поясни, что ты считаешь в Спирите контейнером и что в него запаковывают. Тогда и аналог функции bind (ну или fmap для функтора) будет уже проще найти.
I>Собственно как то иначе сделать спирит будет затруднительно, парсер-комбинаторы сами по себе монадические до безобразия. Парсеры комбинаторы без монад всё равно что промисы без метода then
У меня такое ощущение, что ты обзываешь монадами любой кортеж функций, применяемый последовательно к одному параметру. )))
I>Промисы у которых всегда есть метод then не в счёт ?
Здравствуйте, Ikemefula, Вы писали:
I>Это чтото очень интересное и я шота не пойму, какие вещи не под силу монадам ? В твоём любимом С++ все до одной фичи которые тебе нравятся, это монады встроеные в язык. И этим монадам, оказывается, чтото не под силу
То, что встроено в язык, это на самом деле даже близко не монады. Просто отдалённо напоминающие сущности.
А не под силу им обычно вещи, реализуемые через серьёзное метапрограммирование.
Re[41]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>Тогда непонятно, что означают слова "параметрический полиморфизм же может иметь разные реализация, причём даже в рамках одного языка. Самая эффективная естественно через обобщённое программирование". Параметрический полиморфизм, получается, один из инструментов обобщенного программирования (наряду с мл-ными функторами и т.д.), что означает его реализация через обобщенное программирование тогда.
Обобщённым программированием принято называть конкретную реализацию параметрического полиморфизма, используемую в языках типа C++, Java, C#, D и т.п. Это речь естественно не о внутреннее реализации, т.к. в этом смысле даже перечисленные сильно различаются.
K>Нет. Одна единица компиляции (модуль, например) компилируется в объектный файл и интерфейсный файл, содержащий развертки (код из модуля в той или иной степени прекомпиляции, сериализованное аст, например — ну, не важно). Никаких разверток в файле может вообще не быть — никаких межмодульных оптимизаций тогда не будет, компиляция полностью раздельная. Допустим, что компилировали мы с -O0, для ускорения компиляции во время разработки. Теперь мы хотим получить итоговую версию, и компилируем с -O2 в интерфейсных файлах теперь гораздо больше разверток, а компилятор не стесняется их использовать, когда компилирует зависимые модули. Разумеется, компиляция теперь не полностью раздельная, требует больше времени, но зато есть инлайн и специализация. Противоположная крайность такого подхода, когда весь код всех компилируемых модулей программы оказывается в развертках в интерфейсных файлах — теперь никакой раздельной компиляции нет, есть полнопрограммный анализ, естественно, чудовищно медленный, зато дающий на выходе самый быстрый код.
Хааа, ну так если мы поставляем вместе с объектным файлом ещё и по сути исходник (интерфейсный файл с AST), который потом используется для сборки других модулей, то это вообще то даже близко не компиляция. Это скорее просто некая оптимизация процесса сборки для настроек без оптимизации.
Т.е. получается, что обобщённый код на Хаскеле действительно может компилироваться в самостоятельный двоичный код, но, как тут и указывали, этот код получается тормозной, так что его используют разве что для процесса разработки. Но можно получить и нормальный код, резко забыв про раздельную компиляцию... )))
K>Ну да, "шаблоны не трогаются" а дженерики, параметризуемые модули и т.д. наоборот, проверяются и типизируются. В известно каких языках — всех, кроме C++ и его клонов.
Ну так на то они и дженерики, а не шаблоны и как следствие имеют намного более слабые возможности. И я здесь совсем не про метапрограммирование, а именно про возможности обобщённого программирования.
K>Непонятно, почему это нужно рассматривать случай, в котором куча абстракций не вводится? Тогда в языках вроде хаскеля вообще смысла нет, потому что в них все оптимизировано именно для облегчения построения таких абстракций. Именно для этого существуют, в числе прочего, и то, что вы называете "ритуальными плясками". Если же слои абстракций возводить не нужно, то и языки программирования, кроме самых примитивных не нужны, а различия между ними не существенны. Нужны и существенны только прилежание и усидчивость.
Ага, но опять же в дело вступает вопрос соотношения объёмов. Не думаю, что можно считать хорошей программой такую, в которой большую часть занимают абстракции, а не занятия собственно делом. А делом у нас занимаются как раз вызовы системного апи, последовательности которых нам и надо будет выписывать (и побольше, чем введений всяких абстракций). А именно этот процесс и сопровождается в Хаскеле лишними (по отношению к обычным императивным языкам) плясками.
Re[19]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Klapaucius, Вы писали:
K>В общем-то смысл замыкания как раз в продлении жизни скоупа из которого оно уходит. Так что замыкание и решение UFP — это работающий [&], а не копирование. При сколько нибудь существенном использовании ФП вы устанете ждать, пока там что-то копируется
Ммм не собираюсь влезать в вашу с Евгением непосредственную дискуссию, но после прочтение этого сразу же возник вопрос. Вы конечно же знаете, что в C++ кроме операций "передача ссылки" и операции "копирование" возможна ещё и операция "перемещение"? И что она определена например для всех stl коллекции и для них не особо отличима по потреблению ресурсов от передачи ссылки на коллекцию, хотя при этом происходит полная передача владения?
Re[54]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
_>Какой bind? Где он там? ) Там даже намёков нет на подобное и вообще другого уровня код.
_>Мы там просто указали, что у optional есть функция-член вида "auto ??? () {???(this.value);}", где ??? подставит компилятор. Т.е. какую бы функцию член мы не затребовали от optional, он нам её предоставит, реализовав её через вызов одноимённой функции на внутреннем значение.
Хм. Давайте проверим.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Поэтому, естественно, в случае C++/D лучше воспользоваться встроенными исключениями в язык (причём оптимизированными).
Ну думаю и для optional можно найти узкое место применения в C++, но естественно не в виде монады или чего-то подобного, а вот прямо как в Boost'e. Например для случая, когда nothing является вполне себе регулярным результатом, а не признаком исключительной ситуации. Только тогда планировать его в код изначально и использовать через if.
Re[55]: Есть ли вещи, которые вы прницпиально не понимаете...
S>Какого типа будет у нас выражение LibFunc(Optional!int(20))?
Будет естественно Optional!(Optional!double), но для подобных функций (знающих что такое optional) этот код и не предназначен. Он создан для решения проблемы использования "старого" кода (не знающего optional) без его переписывания.
S>Когда вы почините ваш код, ваш opDispatch будет с точностью до синтаксиса воспроизводить пример Липперта:
С чего бы мне там что-то чинить? Всё работает как и задумывалось.
S>А нужный мне для лифтинга функтор будет строиться из opDispatch (==bind) и this().
То, что из любой монады можно получить функтор — это как бы очевидно. Но монады у нас тут нет.
S>В итоге вы, собственно, опишете монаду, т.к. ваш код будет удовлетворять всем монадным законам.
Максимум это похоже на функтор, но и то странный. Эдакий функтор времени компиляции. )
Re[56]: Есть ли вещи, которые вы прницпиально не понимаете...
S>>Какого типа будет у нас выражение LibFunc(Optional!int(20))?
_>Будет естественно Optional!(Optional!double), но для подобных функций (знающих что такое optional) этот код и не предназначен. Он создан для решения проблемы использования "старого" кода (не знающего optional) без его переписывания.
Проблема в том, что вы заранее не знаете, где какой код. У вас может смешиваться код, "знающий", что такое optional, и код, который ничего о нём не знает. Чтобы это решить, вам придётся либо явно описывать упаковку/распаковку в перегрузках LibFunc, либо всё-таки сделать из Optional монаду.
_>С чего бы мне там что-то чинить? Всё работает как и задумывалось.
Это потому, что вы задумали неправильно. Ваш код работает ровно на одном тестовом примере, а шаг вправо/влево приводит к неожиданностям.
_>То, что из любой монады можно получить функтор — это как бы очевидно. Но монады у нас тут нет. Пока нет. Но и код неуниверсальный. Когда вы его почините, у вас будет полная монада. S>>В итоге вы, собственно, опишете монаду, т.к. ваш код будет удовлетворять всем монадным законам. _>Максимум это похоже на функтор, но и то странный. Эдакий функтор времени компиляции. )
Это и есть функтор (в математическом смысле, а не в смысле С++). Какая разница, в какой момент он работает? Важна семантика.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[53]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, Ikemefula, Вы писали:
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). I>>И что это меняет ?
EP>Вы обсуждали std::future — сейчас там нет .then, так что никакой "путаницы в показаниях" нет. EP>std не единственная библиотека где есть future/promise, и в некоторых из них есть .then — например PPL, Boost.
Это ничего не меняет.
Re[50]: Есть ли вещи, которые вы прницпиально не понимаете...
Здравствуйте, alex_public, Вы писали:
I>>Это не важно, главное что монада и такой вот функтор в языке без поддержки монад есть практически одно и то же.
_>А что за таинственная поддержка монад языком? ) Уже который раз от тебя слышу, но так что-то и не понял.
do
x <- return 1
y <- return 2
z <- return 3
return x + y + z
Всё очевидно, по моему. Первый вариант в своём уме никто не будет писать. Есть, правда, исключения, которые подтверждают правило.
Поддержка языком делается
1. на уровне синтаксиса
2. на уровне библиотеки
Тоы ты продолжай делать вид, что слышишь такое впервые, очень интересно
_>И между функтором и монадой есть большая разница в любом языке. Мы можем сделать из монады функтор (не меняя код!), но не можем сделать из функтора монаду. Во всяком случае без введения ещё одной особой операции (т.е. опять же правки кода).
А сравнивая одно с другим на фоне императивного кода получаем ничтожную разницу. Скажем к императивному коду ты хоть сотню операций добавь, монаднее он не станет, это все равно будет императивный код.
I>>Более того, такой метод есть уже в спирите, даже целых несколько, например binary_operator::eval или unary_operator::eval
_>В случае монады или даже функтора, у нас имеется некий контейнерный тип, в который запаковываются некие внутренние типы. Вот поясни, что ты считаешь в Спирите контейнером и что в него запаковывают. Тогда и аналог функции bind (ну или fmap для функтора) будет уже проще найти.
тоже самое, что в любых других реализациях парсер-комбинаторов, т.к. парсер требует хранения состояния и я уже показывал.
I>>Собственно как то иначе сделать спирит будет затруднительно, парсер-комбинаторы сами по себе монадические до безобразия. Парсеры комбинаторы без монад всё равно что промисы без метода then
_>У меня такое ощущение, что ты обзываешь монадами любой кортеж функций, применяемый последовательно к одному параметру. )))
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>C++ предоставляет выбор — можно сделать move, можно copy, можно ref-counted, а можно вообще зарегистрировать deleter в scope повыше. Причём это работает для любых ресурсов, а не только для памяти как в языках с GC.
Выбор-то он предоставляет, вот только того варианта, который лучше всего подходит для ФП кода — нормального ГЦ — среди предложенных нет.
EP>Можно ли вызвать замыкание несколько раз? Когда конкретно происходит удаление?
А! До меня только сейчас дошло (простите, медленно соображаю), что нужно было сделать:
let foo () =
let x = 5
use file = new Movable<_>(System.IO.File.CreateText(@"d:\log7.txt"))
(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)let y = 11
let f = file.Move()
(* failwith "BANG!" //а тут сборщиком мусора *)
DispFun.Create(f, fun s ->
(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
f.Write(s + "!")
x + y)
let bar () =
let f = foo ()
use d = DispFun.ToDisposable(f)
f "OMG"(* failwith "BANG!" //тут ресурс будет освобожден детерминированно *)
f "OMG!"
f "OMG!!"
bar()
В принципе, F# функция могла бы имплементировать IDisposable, если захватывает в замыкание что-то имплементирующее IDisposable. Технических проблем тут, на первый взгляд, нет, но это не сделано. Пришлось мне это сделать самому, отнаследовавшись от FSharpFunc:
using System;
using Microsoft.FSharp.Core;
namespace DisposableFunction
{
public static class DispFun
{
public static FSharpFunc<T, R> Create<T, R>(IDisposable disp, FSharpFunc<T, R> f)
{
return new DispFun<T, R>(disp, f);
}
public static IDisposable ToDisposable<T, R>(FSharpFunc<T, R> f)
{
return f as IDisposable;
}
}
public class DispFun<T,R> : FSharpFunc<T,R>, IDisposable
{
private readonly IDisposable _disp;
private readonly FSharpFunc<T, R> _f;
public DispFun(IDisposable disp, FSharpFunc<T, R> f)
{
_disp = disp;
_f = f;
}
public override R Invoke(T x)
{
return _f.Invoke(x);
}
public void Dispose()
{
_disp.Dispose();
// Console.WriteLine("func.Dispose");
}
}
}
EP>Работающего [&] при передачи вверх естественно не будет. Но говорить что работающий [&] это необходимое условие для решения UFP — неправильно. Вот например цитата из wiki: EP>
EP>Another solution is to simply copy the value of the variables into the closure at the time the closure is created.
В разных языках разные подходы
С практической точки зрения, наиболее полезный способ — это именно нормальный [&].
K>>При сколько нибудь существенном использовании ФП вы устанете ждать, пока там что-то копируется, EP>Копировать необязательно, часто достаточно move. EP>Во-первых в большинстве полезных случаев ref-counted не нужен
С моей точки зрения, копирование и передача владения удовлетворяют всяким предельным и маргинальным случаям. Какой смысл вообще связываться с ФП, если оно останется на игрушечном уровне и придется ходить держась за стенку? Никакого нормального перехода к высокоуровневому коду не будет, если программист не получит абстракции, которые просто работают.
K>>да и смысл замыкания часто именно в том, чтоб разные функции ушедшие из одного скоупа впоследствии работали друг с другом через замыкание на него. Т.е. в большинстве практически полезных случаев придется использовать тормозные и замусоривающие код умные указатели со счетчиками.
EP>код не замусоривается: EP>
EP>Foo x(1,2);
EP>x.bar();
EP>x.baz();
EP>// vs
EP>auto x = make_shared<Foo>(1, 2);
x->>bar();
x->>baz();
EP>
Конечно он замусоривается, если для чего-то использующегося как правило нужно писать и читать дополнительную писанину, а для исключений — не нужно. Должно быть наоборот.
K>>Не говоря уж о том, что в ФП циклические ссылки в порядке вещей.
EP>Они действительно возможны, но разве это прям в порядке вещей? Особенно учитывая иммутабельность.
Да, в порядке вещей. Особенно учитывая, что ленивость — это такая замаскированная мутабельность, которая позволяет сконструировать иммутабельную структуру с элементами "из будущего".
K>>Вообще говоря, тут утечка ресурса. EP>Где?
Вы возвращаете функцию, владеющую открытым ресурсом. В реальном ФП коде ее время жизни будет определятся динамически, а не статически по лексическому скоупу и т.д. В результате никакого преимущества перед финализацией ГЦ тут нет. Вообще, я считаю, что захват и передача вверх портит все автоматическое управление ресурсами с помощью стека. Это для нее плохой сценарий, на который такая система не расчитана, а в ФП он типичный.
EP>Это только иллюстрация проблемы UFP, естественно в большинстве use-case'ов это замыкание будет вызываться несколько раз, и возможно будет передаваться ещё выше. Например: EP>
EP>{
EP> auto log = make_log("...");
EP> log(1);
EP> log("1");
EP> log(foo);
EP>} // log destructor cleans up its resources
EP>
Как сделать подобное в C# или Haskell?
См. мой код на F# выше.
K>>На практике функция будет болтаться в памяти как часть какого-то замыкания или структуры данных, хранящих функции и возможно вовсе никогда не будет вызвана. Т.е. момент ее выполнения будет определен динамически, ни о каком детерминированном высвобождении ресурса тут речи не идет.
EP>Выше use-case.
Ну я же не отрицаю случаев, когда она не будет. Но из возможности поддерживать какие-то маргинальные ФП-сценарии не следует поддержка ФП. Это ведь самый минимум, а уже начинаются оговорки.
Был такой проект реализации ФЯ с управлением памятью с выводом регионов — MLkit. Такой подход — это более универсальный, чем управление с помощью стека — его надмножество по возможностям. Тем не менее, функциональные программы в такое прокрустово ложе не легли и оказалась распространенной ситуацией, когда память выделяется в общем регионе — т.е. проще говоря, утекает, потому, что этот регион "открывается" в начале работы программы, а "закрывается", соответственно, в конце. Пришлось добавлять ГЦ, так как фактически оказалось, что в ФП статическое управление памятью (по областям видимости) годится для уменьшения нагрузки на ГЦ — но не более того.
EP>В языках где есть готовые решения только для памяти, но нет ничего для остальных ресурсов — действительно, такой номер не пройдёт.
И чем в этом общем случае будет лучше ваше средство управления ресурсами, которое даже для памяти решения не дает?
EP>Потому что GC для памяти это полумера, и не решает UFP в общем случае
UFP как раз и решает. А то, что решение UFP само по себе создает сложности для точного и детерминированного управления ресурсами — ну так жизнь это не только музыка и цветы.
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