Наши проекты:
Журнал · Discuz!ML · Wiki · DRKB · Помощь проекту |
||
ПРАВИЛА | FAQ | Помощь | Поиск | Участники | Календарь | Избранное | RSS |
[18.232.169.110] |
|
Страницы: (6) « Первая ... 3 4 [5] 6 все ( Перейти к последнему сообщению ) |
Сообщ.
#61
,
|
|
|
Ну вот простой пример(имей в виду, что return - это не императивный return, а функция, которая, грубо говоря, заворачивает значение в нашу монаду):
-- Функция, которая возвращает действие ввода/вывода, -- в котором завернут Nothing, явно типизированный как Maybe Int fun1 :: IO (Maybe Int) fun1 = return (Nothing) -- функция проверки testIO = do -- здесь будет вызван fail, т.к. ждем Just, а приходит Nothing: (Just x) <- fun1 print x В монаде IO fail кидает ошибку. Проверяем: main = testIO main: user error (Pattern match failure in do expression... Теперь возьмем другую монаду, где fail означает не ошибку, а какое-то значение. Тут подойдет список: -- делаем тоже самое: fun2 :: [Maybe Int] fun2 = return (Nothing) -- тоже что [Nothing] -- Проверяем список testList = do -- здесь будет вызван fail, т.к. ждем Just, а приходит Nothing: (Just x) <- fun2 return (x) Для списка fail просто возвращает пустой список. Проверяем: main = print testList [] Добавлено Если что, Maybe - это тип, который имеет два конструктора: data Maybe a = Just a | Nothing Соответственно, в наших тестах мы выполняем сопоставление с образцом и ждем конструктор Just, а приходит туда Nothing. В случае монадического вычисления с IO мы получаем ошибку, а для монадического вычисления со списками - пустой список. Поскольку в C++ нет сопоставления с образцом, пользы от fail нет никакой. А понимание монад он никак не проясняет(скорее мешает). |
Сообщ.
#62
,
|
|
|
Цитата D_KEY @ Поскольку в C++ нет сопоставления с образцом, пользы от fail нет никакой. А может ли служить в Цэ++ в качестве "сопоставления с образцом" - std::type_info (в частности std::type_info::before)? Если конечно я правильно начинаю осознавать реальность В случае наследования с использованием RTTI, мы сможем вырвать кусок у объекта на произвольном уровне иерархии (с помощью его конструктора). А вот если нужный нам конструктор отсутствует в иерархии - тогда и нужно выкидывать fail. Кстати, а как его в Цэ++ реализовать, исключение бросить штоле? Извини, что достаю вопросами. Просто пока не могу распознать ценности всего этого для меня. Попутно еще одни вопрос. Многие в процессе моделирования используют/манипулируют понятиями Шаблоны Проектирования (ну или Паттерны). И это безотностительно к какому-то конкретному ЯП. Рассматриваемые "монады" какой(или какие совокупности) из паттернов реализуют? И вообще можно ли так говорить относительно монад? |
Сообщ.
#63
,
|
|
|
Да любой код на C++ уже считай монада. Операции идут одна за другой - чем не монада?
|
Сообщ.
#64
,
|
|
|
Цитата applegame @ Да любой код на C++ уже считай монада. Операции идут одна за другой - чем не монада? Ну да, в принципе, монада. Почти IO, только захардкоженная. Другое дело, что писать свои монады в таком же виде нельзя. Да и смысла считать это монадой особого нет. C++ изначально ничего не говорит о чистоте. |
Сообщ.
#65
,
|
|
|
Списки тоже монады.
Потому можно написать: fun = do x <- [1, 2, 3] y <- [4, 5, 6] return (x, y) main = print fun И получить: [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)] На самом деле тут использована do-нотация, которая представляет из себя синтаксический сахар. Рассахаривается это в fun = [1, 2, 3] >>= \x -> [4, 5, 6] >>= \y -> return (x, y) Т.е. так: fun = [1, 2, 3] >>= (\x -> [4, 5, 6] >>= (\y -> return (x, y))) Что на крестах выглядит примерно так: auto res = mk_list({1, 2, 3}) >>= [](auto x) { return mk_list({4, 5, 6}) >>= [=](auto y) { return ret<list>(std::make_pair(x, y)); }; }; С тем же результатом: [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)] Для этого нужно сделать списки и реализовать для них монаду. список(на основе вектора) template<typename T> struct list : private std::vector<T> { using impl = std::vector<T>; using impl::value_type; list() = default; list(std::initializer_list<T> il) : std::vector<T>(std::move(il)) { } using impl::push_back; using impl::insert; using impl::begin; using impl::end; }; auto mk_list(std::initializer_list<T> il) -> list<T> { return list<T>(il); } Реализуем монаду для списка: template<> template<typename T> auto Monad<list>::ret(T a) -> list<T> { // заворачиваем объект в список return list<T>{a}; } template<> template<typename T, typename U> auto Monad<list>::bind(list<T> ma, std::function<list<U>(T)> f) -> list<U> { // формируем список списков путем применения к каждому элементу переданной функции // (которая принимает объект, а возвращает монаду(список)) list<list<U> > tmp; std::transform(ma.begin(), ma.end(), std::back_inserter(tmp), f); // конкатенируем списки в один list<U> res; for (auto & v : tmp) { res.insert(res.end(), v.begin(), v.end()); } return res; } Ну и для вывода пара вспомогательных функций: вывод пар и списков template<typename T, typename U> std::ostream & operator << (std::ostream & os, std::pair<T,U> p) { os << "(" << p.first << "," << p.second << ")"; return os; } template<typename T> std::ostream & operator << (std::ostream & os, const list<T> & a) { os << "["; int i = 0; for (auto & e : a) { if (i++) os << ","; os << e; } os << "]"; return os; } http://ideone.com/bJjEW3 |
Сообщ.
#66
,
|
|
|
Теперь по поводу IO.
Смысл IO в добавлении работы с побочными эффектами в чистый язык. Одним из способов(в каком-то смысле теоретическим) представления побочных эффектов в чистом языке является работа с некоторым объектом, представляющим "реальный мир". И каждое действие с побочными эффектами в чистом языке мы будем представлять в виде функций вида (real_world) -> (real_world, data). Т.е. мы как будто не меняем внешние условия, а возвращаем новое состояние мира вместе с каким-то значением. IO<T> как раз представляет собой обертку над (real_world) -> (real_world, T) и позволяет через монадический интерфейс работать с вычислениями, "меняющими мир"(через якобы возврат "нового мира"). В Haskell тип RealWorld скрыт от пользователя, как и внутренняя структура IO, т.е. наружу торчит только тип IO a, но запаковываем значение мы через return, а достаем значение только внутри монадического вычисления(как и в других монадах - через лямбду или <- в do-нотации). Теперь возвращаемся к C++. struct real_world { explicit real_world(size_t i = 0) : m_i(i) {} real_world(const real_world &) = delete; real_world(real_world && other) : m_i(other.m_i) {} const real_world & operator = (const real_world & other) = delete; const real_world & operator = (real_world && other) = delete; // "меняем мир" // принимаем функцию с побочными эффектами, // возвращаем пару из "нового мира" и результата функции template<typename T, typename F> std::pair<real_world, T> change(F f) const { return std::make_pair(real_world{m_i + 1}, f()); } private: const size_t m_i; }; IO: template<typename T> struct IO { // инкапсулируем функцию, маскирующую побочные эффекты через возврат "нового мира" using f_type = std::function<std::pair<real_world, T>(real_world)>; explicit IO(f_type f) : m_f(std::move(f)) { } // запускаем функцию, которая порождает "новый мир" и значение auto run(real_world w) const -> std::pair<real_world, T> { return m_f(std::move(w)); } private: f_type m_f; }; Реализуем монаду: template<> template<typename T> auto Monad<IO>::ret(T a) -> IO<T> { // создаем IO с функцией, которая просто создает пару из "мира" и значения, // не производя побочных вычислений и не меняя мир return IO<T>([=](real_world w){return std::make_pair(std::move(w), a);}); } template<> template<typename T, typename U> auto Monad<IO>::bind(IO<T> ma, std::function<IO<U>(T)> f) -> IO<U> { // возвращаем IO с функцией, которая принимает "мир", // запускает на нем первое вычисление, получает "следующий мир" и новое значение, // затем вычисляем функцию от значения, получая новое IO вычисление, // которое и запускаем над "следующим миром" return IO<U>([=](real_world w) { auto world_and_data = ma.run(std::move(w)); auto next_io = f(world_and_data.second); return next_io.run(std::move(world_and_data.first)); }); } Теперь определим простейшие функции с IO, работающие через реальные функции с побочными эффектами // заводим тип для "ничего", чтобы не заморачиваться с обработкой void struct unit {}; auto putChar(char ch) -> IO<unit> { // Создаем IO с функцией, которая принимает "мир" // и выводит символ на экран, возвращая новый "мир" // (см. real_world::change) return IO<unit>([=](real_world w) { return w.change<unit>([=]{ std::cout << ch; return unit{}; }); }); }; auto getChar(unit) -> IO<char> { // Создаем IO с функцией, которая принимает "мир" // и читает символ, возвращая новый "мир" и прочитанный символ // (см. real_world::change) return IO<char>([=](real_world w) { return w.change<char>([=]{ char res; if (!std::cin.get(res).good()) return '\0'; // пока так return res; }); }); } Теперь можно уже что-то сделать просто через IO и эти две функции. Для начала реализуем вывод строк через putChar. auto putLine(std::string str) -> IO<unit> { // если строка пустая, то возвращаем действие ввода-вывода, // которое выводит символ '\n', // и останавливаем рекурсию if (str.empty()) { return putChar('\n'); } // возвращаем действие ввода-вывода, которое сначала выводит первый символ строки, // затем рекурсивно выводит остаток строки return putChar(str[0]) >> putLine(str.substr(1)); } Теперь функцию считывания строки: auto getLine(unit) -> IO<std::string> { // возвращаем действие ввода-вывода, которое считывает первый символ, // и если он означает конец строки, // то возвращает действие ввода-вывода, которое вернет пустую строку, // в противном же случае считываем(рекурсивно) строку и возвращаем действие ввода-вывода, // которое вернет сконкатенированную строку из принятого ранее символа и новой строки return getChar(unit{}) >>= [=](char ch) { if (ch == '\n' || ch == '\r' || ch == '\0') { return ret<IO>(std::string()); } return getLine(unit{}) >>= [=](std::string str) { return ret<IO>(ch + str); }; }; } Ну и проверяем. Вот такой простой код в do-нотации: main = do putLine "What is your name?" name <- getLine putLine ("Nice to meet you, " ++ name ++ "!") будет выглядеть вот так: int main() { // формируем вычисление ввода-вывода auto io_main = putLine("What is your name?") >> getLine(unit{}) >>= [=](std::string name) { return putLine("Nice to meet you, " + name + "!"); }; // запускаем вычисление io_main.run(real_world{}); } http://ideone.com/WYK5GA |
Сообщ.
#67
,
|
|
|
ХЗ зачем я все это делал, но было интересно
|
Сообщ.
#68
,
|
|
|
D_KEY
Да уж, продемонстрировали недюжинные познания. Жаль что во флейме, а так бы получили бы от меня +1 в тематике. Придется это делать во флейме |
Сообщ.
#69
,
|
|
|
Цитата D_KEY @ будет выглядеть вот так: |
Сообщ.
#70
,
|
|
|
Цитата shm @ Цитата D_KEY @ будет выглядеть вот так: Ну do-нотация это просто сахар. На самом деле компилятор haskell сначала превратит main = do putStrLn "What is your name?" name <- getLine putStrLn ("Nice to meet you, " ++ name ++ "!") в main = putStrLn "What is your name?" >> getLine >>= \name -> putStrLn ("Nice to meet you, " ++ name ++ "!") а потом уже будет с этим работать. Тут разница с кодом auto io_main = putLine("What is your name?") >> getLine(unit{}) >>= [=](auto name) { return putLine("Nice to meet you, " + name + "!"); }; Не такая уж и большая, да? do-нотацию для С++ придумать сложнее... Сишные "макросы" тут слабоваты, а шаблоны синтаксис объявлений не поменяют. Добавлено Но вообще это бесполезная для С++ штука. Монада для списков и то полезнее. |
Сообщ.
#71
,
|
|
|
Цитата D_KEY @ Не такая уж и большая, да? Не сильна большая, но мозг выносит сильнее. |
Сообщ.
#72
,
|
|
|
Вот тут как раз в тему
А вот тут даже я кое что понял. Но вот этого я не могу понять. Да, ведь можно нагородить операторов типа например "<>+->-" совершенно бессмысленных с точки зрения общепринятого понимания их составляющих - и потом гордо объявить, что это - операция "приковывать цепью"(с) хрен знает что к хрен знает чему. Понятно, что любой язык программирования есть чистейшей воды формальная система. Но должны же быть и некие пределы. С этой точки зрения всем знакомый нам знак интеграла есть пример очевидной формализации целого алгоритма!!! - но совершенно осмысленный и понятный. Именно это обстоятельство меня и настораживает. Слишком субъективны термины, понятия, операции и прочие процессы. А это описание вообще шедевр: Цитата В функциональном программировании монада - структура, которая представляет вычисления, определенные как последовательности шагов: тип со структурой монады определяет то, что это означает приковывать цепью операции или функции гнезда того типа вместе. Это позволяет программисту строить трубопроводы, которые обрабатывают данные в шагах, в которых каждое действие украшено дополнительными правилами обработки, предоставленными монадой. После трубопровода надо вводить категории вентиля, заглушки, датчиков давления и прочая...потом перейдем к канализации - в смысле объединения трубопроводов как каналов потоков информации...задачи фильтрации потоков канализации и тд... PS В потоковом программировании тоже есть интересные нововведения, как например "цвет данных" Какой то философ сказал "ни одна вещь не должна объясняться сложнее чем она есть на самом деле" |
Сообщ.
#73
,
|
|
|
О, там как раз для future реализация, то, что MyNameIsIgor говорил.
А вот do-нотацию я что-то не увидел... |
Сообщ.
#74
,
|
|
|
Цитата JoeUser @ Читал доку по Эрлангу, видел там вычисление функции как последовательность утверждений, де жа вю ... Пролог. Э-м... Можешь слегка по-подробней? А то у меня возникает стойкое ощущение, что тебя слегка ввела в заблуждение Прологоподобная грамматика Эрланга. Добавлено Цитата D_KEY @ Но вообще это бесполезная для С++ штука. Монада для списков и то полезнее. А вот чистому Си не повредили бы, чтобы проверки кодов ошибок синтаксически лучше выглядели б. В Rust, я так понимаю, это сделали через макрос try! ? Добавлено Цитата D_KEY @ do-нотацию для С++ придумать сложнее... Сишные "макросы" тут слабоваты, а шаблоны синтаксис объявлений не поменяют. А оператор ; переопределить нельзя? Я вроде уже спрашивал этот вопрос, может, не у тебя, но на этом форуме. |
Сообщ.
#75
,
|
|
|
Цитата korvin @ А оператор ; переопределить нельзя? Я вроде уже спрашивал этот вопрос, может, не у тебя, но на этом форуме. Нет, но operator , можно Только я тут больше не о простой цепочке, а о <-. Т.е. даже если бы мы перегрузили ; , это бы не избавило нас от лямбд. |