#include <functional>
#include <utility>
#include <iostream>
template<template<class ...> class M>
struct Monad {
// return :: a -> m a
template<typename T>
static auto ret(T a) -> M<T>;
// bind :: m a -> (a -> m b) -> m b
template<typename T, typename U>
static auto bind(M<T>, std::function<M<U>(T)>) -> M<U>;
};
// return :: a -> m a
template<template<class...> class M, typename T>
auto ret(T a) -> M<T>
{
return Monad<M>::ret(std::move(a));
}
// (>>=) :: m a -> (a -> m b) -> m b
template<template<class...> class M, typename T, typename F>
auto operator >>= (M<T> ma, F fun) -> decltype(fun(std::declval<T>())) // запутанно, но не знаю, как сделать лучше, чтобы работали лямбды
{
using R = decltype(fun(std::declval<T>()));
return Monad<M>::bind(ma, std::function<R(T)>(std::move(fun)));
}
// (>>) :: m a -> m b -> m b
template<template<class...> class M, typename T, typename U>
auto operator >> (M<T> ma, M<U> mb) -> M<U>
{
return ma >>= [=](T){ return mb; };
}
// -----
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;
};
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));
});
}
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;
});
});
}
// -----
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);
};
};
}
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{});
}