#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{});
}
