Skip to content

Instantly share code, notes, and snippets.

@kaoskorobase
Last active October 30, 2015 11:43
Show Gist options
  • Save kaoskorobase/11260139 to your computer and use it in GitHub Desktop.
Save kaoskorobase/11260139 to your computer and use it in GitHub Desktop.
Cont monad with error handling in C++

So I've been looking at the new NSURLSession API and imagining the tangled mess of delegate methods and completion blocks blurred my vision and made my head spin. After regaining my composure I remembered the wonderful world of Haskell monad transformer stacks and in particular I remembered two blog posts I came across some time ago (1 and 2) about restructuring nested callbacks with the Cont(inuation) monad. Digging further, I found an excellent article about the continuation monad in C++.

This is an attempt at generalizing some of the definitions, in particular making Cont (Continuator in the article) a typedef for a std::function and expanding the definition of bind to allow a change from one continuation argument type to another. I've also added error handling by wrapping the continuation arguments in a boost::variant<std::exception_ptr,T>; this is effectively an ContT / Error monad stack but implemented explicitly (monad transformers in C++ anyone?).

As a first impression, I would say that the monadic code greatly improves composability, i.e. the possibility of creating small building blocks (e.g. a single file download) that can be combined into larger structures (e.g. downloading a JSON resource and associated media files). This goes at the expense of readability - it's plain ugly in C++, as there's no special do syntax support - and writability - my implementation yields mostly incomprehensible compiler errors if you get a type wrong.

Unfortunately it doesn't play well with NSURLSession background downloads - serializable continuations would be needed for this to work - and it's not used it in production anymore.

Compile the code with:

c++ -std=c++11 -stdlib=libc++ -Ipath/to/boost/ -g -o cont Cont.cpp

Cont.hpp Cont.cpp

Comments welcome! @kaoskorobase

#include "Cont.hpp"
#include <iostream>
#include <thread>
#include <chrono>
#include <list>
#include <boost/variant.hpp>
using namespace HearHearMe;
using namespace std;
Cont<void,string> asyncApi(string tag)
{
return [tag](function<void(Result<string>)> handler) {
thread th([tag,handler]()
{
cout << "Started async " << tag << "\n";
this_thread::sleep_for(chrono::seconds(3));
handler(Result<string>(tag));
});
th.detach();
};
}
void main_4()
{
Cont<void,Void> action = sequence<Void,std::list<Cont<void,Void>>>({
bind<void,string,Void>(asyncApi("1"), [](string tag) {
cout << "Done " << tag << endl;
return pure<void>();
}),
bind<void,string,Void>(asyncApi("2"), [](string tag) {
cout << "Done " << tag << endl;
return pure<void>();
}),
fail<void,Void>("blah"),
bind<void,string,Void>(asyncApi("3"), [](string tag) {
cout << "Done " << tag << endl;
return pure<void>();
})
});
thread([action](){
exec<Void>(
action,
[](Void) { std::cout << "Success\n"; },
[](std::exception_ptr) { std::cout << "Failure\n"; }
);
}).detach();
// run counter in parallel
for(int i = 0; i < 2000000; ++i)
{
cout << i << endl;
this_thread::sleep_for(chrono::seconds(1));
}
}
int main()
{
main_4();
return 0;
}
// Copyright 2014 Samplecount S.L.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef HEARHEARME_CONT_HPP_INCLUDED
#define HEARHEARME_CONT_HPP_INCLUDED
#include <boost/variant.hpp>
#include <exception>
#include <functional>
#include <list>
#include <string>
#include <tuple>
namespace HearHearMe {
template <typename A> using Result = boost::variant<std::exception_ptr,A>;
template <typename R, typename A> using Cont = std::function<R(std::function<R(Result<A>)>)>;
template <typename R, typename A>
Cont<R,A> pure(A x)
{
return [=](std::function<R(Result<A>)> k){
return k(Result<A>(x));
};
}
struct Void {};
template <typename R>
Cont<R,Void> pure()
{
return [](std::function<R(Void)> k){
return k(Void());
};
}
template <typename R, typename A>
Cont<R,A> fail(std::exception_ptr eptr)
{
return [=](std::function<R(Result<A>)> k) {
return k(Result<A>(eptr));
};
}
template <typename R, typename A>
Cont<R,A> fail(std::string msg)
{
return [=](std::function<R(Result<A>)> k) {
try
{
throw std::runtime_error(msg);
}
catch (std::exception&)
{
return k(Result<A>(std::current_exception()));
}
return k(Result<A>(std::exception_ptr()));
};
}
namespace detail
{
template <typename R, typename A, typename B> class BindResultVisitor : public boost::static_visitor<Cont<R,B>>
{
std::function<Cont<R,B>(A)> m_cont;
public:
BindResultVisitor(std::function<Cont<R,B>(A)> cont)
: m_cont(cont)
{}
Cont<R,B> operator()(std::exception_ptr eptr) const
{
return fail<R,B>(eptr);
}
Cont<R,B> operator()(A x) const
{
return m_cont(x);
}
};
template <typename A> class ExecResultVisitor : public boost::static_visitor<void>
{
std::function<void(A)> m_success;
std::function<void(std::exception_ptr)> m_failure;
public:
ExecResultVisitor(std::function<void(A)> success, std::function<void(std::exception_ptr)> failure)
: m_success(success)
, m_failure(failure)
{}
void operator()(std::exception_ptr eptr) const
{
return m_failure(eptr);
}
void operator()(A x) const
{
return m_success(x);
}
};
}
template <typename R, typename A, typename B>
Cont<R,B> bind(Cont<R,A> ktor, std::function<Cont<R,B>(A)> rest)
{
return [=](std::function<R(Result<B>)> k) {
// std::function<Cont<R,A>(A)> _rest = rest;
std::function<R(Result<A>)> lambda = [k,rest](Result<A> a) {
return boost::apply_visitor(detail::BindResultVisitor<R,A,B>(rest), a)(k);
};
return ktor(lambda);
};
};
template <typename R, typename A, typename B>
Cont<R,B> fmap(std::function<B(A)> f, Cont<R,A> m)
{
return bind<R,A,B>(m, [f](A a) {
return pure<R,B>(f(a));
});
}
template <typename A>
void exec(Cont<void,A> m, std::function<void(Result<A>)> f=nullptr)
{
m(f ? f : [](Result<A>){});
}
template <typename A>
void exec(Cont<void,A> m, std::function<void(A)> success, std::function<void(std::exception_ptr)> failure)
{
std::function<void(Result<A>)> handler = [=](Result<A> result) {
boost::apply_visitor(detail::ExecResultVisitor<A>(success, failure), result);
};
exec(m, handler);
}
namespace detail
{
template <typename A, class C>
Cont<void,A> sequence(std::shared_ptr<C> xs, typename C::iterator it)
{
return [=](std::function<void(Result<A>)> k) {
if (it != xs->end())
{
bind<void,A,A>(*it, [xs,it](A) {
auto it_ = it;
return sequence<A>(xs, ++it_);
})(k);
}
};
}
}
template <typename A, class C>
Cont<void,A> sequence(C xs)
{
auto pxs = std::make_shared<C>(xs);
return detail::sequence<A>(pxs, pxs->begin());
}
};
#endif // HEARHEARME_CONT_HPP_INCLUDED
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment