/* Copyright (c) 2013 Microsoft Corporation. All rights reserved. Released under Apache 2.0 license as described in the file LICENSE. Author: Leonardo de Moura */ #pragma once #include #include "util/interrupt.h" #include "util/lazy_list.h" #include "util/list.h" namespace lean { template void for_each(lazy_list l, F && f) { while (true) { auto p = l.pull(); if (p) { f(p->first); l = p->second; } else { break; } check_interrupted(); } } /** \brief Create a lazy list that contains the first \c sz elements in \c l. */ template lazy_list take(unsigned sz, lazy_list const & l) { if (sz == 0 || l.is_nil()) { return lazy_list(); } else { return mk_lazy_list([=]() { auto p = l.pull(); if (p) return some(mk_pair(p->first, take(sz - 1, p->second))); else return p; }); } } /** \brief Create a lazy list based on the list \c l. */ template lazy_list to_lazy(list l) { if (l) { return mk_lazy_list([=]() { return some(mk_pair(head(l), to_lazy(tail(l)))); }); } else { return lazy_list(); } } /** \brief Appends the given lazy lists. */ template lazy_list append(lazy_list const & l1, lazy_list const & l2, char const * cname = "lazy list") { if (l1.is_nil()) return l2; else if (l2.is_nil()) return l1; else return mk_lazy_list([=]() { auto p = l1.pull(); if (!p) { check_system(cname); return l2.pull(); } else { return some(mk_pair(p->first, append(p->second, l2, cname))); } }); } /** \brief Return \c l1 if l1 is not empty, and \c l2 otherwise. */ template lazy_list orelse(lazy_list const & l1, lazy_list const & l2, char const * cname = "lazy list") { if (l1.is_nil()) return l2; else return mk_lazy_list([=]() { auto p = l1.pull(); if (!p) { check_system(cname); return l2.pull(); } else { return p; } }); } /** \brief "Fair" version of \c append. That is, the elements of \c l1 and \c l2 are interleaved. */ template lazy_list interleave(lazy_list const & l1, lazy_list const & l2, char const * cname = "lazy list") { if (l1.is_nil()) return l2; else if (l2.is_nil()) return l1; else return mk_lazy_list([=]() { auto p = l1.pull(); if (!p) { check_system(cname); return l2.pull(); } else { return some(mk_pair(p->first, interleave(l2, p->second, cname))); } }); } /** \brief Create a lazy list by applying \c f to the elements of \c l. */ template lazy_list map(lazy_list const & l, F && f, char const * cname = "lazy list") { if (l.is_nil()) return l; else return mk_lazy_list([=]() { auto p = l.pull(); if (!p) { return p; } else { check_system(cname); return some(mk_pair(f(p->first), map(p->second, f, cname))); } }); } /** \brief Create a lazy list by applying \c f to the elements of \c l. */ template lazy_list map2(lazy_list const & l, F && f, char const * cname = "lazy list") { if (l.is_nil()) return lazy_list(); else return mk_lazy_list([=]() { typename lazy_list::maybe_pair p = l.pull(); if (!p) { return typename lazy_list::maybe_pair(); } else { check_system(cname); return some(mk_pair(f(p->first), map2(p->second, f, cname))); } }); } /** \brief Create a lazy list that contains only the elements of \c l that satisfies \c pred. \remark Lazy lists may be infinite, and none of them may satisfy \c pred. \remark \c check_system() is invoked whenever \c pred returns false. */ template lazy_list filter(lazy_list const & l, P && pred, char const * cname = "lazy list") { if (l.is_nil()) return l; else return mk_lazy_list([=]() { auto p = l.pull(); if (!p) { return p; } else if (pred(p->first)) { return p; } else { check_system(cname); return filter(p->second, pred, cname).pull(); } }); } /** \brief Auxiliary template for \c map_append. */ template lazy_list map_append_aux(lazy_list const & h, lazy_list const & l, F && f, char const * cname) { return mk_lazy_list([=]() { auto p1 = h.pull(); if (p1) { return some(mk_pair(p1->first, map_append_aux(p1->second, l, f, cname))); } else { check_interrupted(); auto p2 = l.pull(); if (p2) { check_system(cname); return map_append_aux(f(p2->first), p2->second, f, cname).pull(); } else { return typename lazy_list::maybe_pair(); } } }); } /** \brief Applies \c f to each element of \c l. The function \c must return a lazy_list. All lazy_lists are appended together. */ template lazy_list map_append(lazy_list const & l, F && f, char const * cname = "lazy list") { return map_append_aux(lazy_list(), l, f, cname); } template lazy_list repeat(T const & v, F && f, char const * cname = "lazy list") { return mk_lazy_list([=]() { auto p = f(v).pull(); if (!p) { return some(mk_pair(v, lazy_list())); } else { check_system(cname); return append(repeat(p->first, f, cname), map_append(p->second, [=](T const & v2) { return repeat(v2, f, cname); }, cname), cname).pull(); } }); } template lazy_list repeat_at_most(T const & v, F && f, unsigned k, char const * cname = "lazy list") { return mk_lazy_list([=]() { if (k == 0) { return some(mk_pair(v, lazy_list())); } else { auto p = f(v).pull(); if (!p) { return some(mk_pair(v, lazy_list())); } else { check_system(cname); return append(repeat_at_most(p->first, f, k - 1, cname), map_append(p->second, [=](T const & v2) { return repeat_at_most(v2, f, k - 1, cname); }, cname), cname).pull(); } } }); } /** \brief Return a lazy list such that only the elements that can be computed in less than \c ms milliseconds are kept. That is, it uses a timeout for the \c pull method in the class lazy_list. If the \c pull method timeouts, the lazy list is truncated. \remark the \c method is executed in a separate execution thread. \remark \c check_ms is how often the main thread checks whether the child thread finished. */ #if !defined(LEAN_MULTI_THREAD) template lazy_list timeout(lazy_list const & l, unsigned, unsigned) { return l; } #else template lazy_list timeout(lazy_list const & l, unsigned ms, unsigned check_ms = g_small_sleep) { if (check_ms == 0) check_ms = 1; return mk_lazy_list([=]() { typename lazy_list::maybe_pair r; atomic done(false); interruptible_thread th([&]() { try { r = l.pull(); } catch (...) { r = typename lazy_list::maybe_pair(); } done = true; }); try { auto start = chrono::steady_clock::now(); chrono::milliseconds d(ms); chrono::milliseconds small(check_ms); while (!done) { auto curr = chrono::steady_clock::now(); if (chrono::duration_cast(curr - start) > d) break; check_interrupted(); this_thread::sleep_for(small); } th.request_interrupt(); th.join(); if (r) return some(mk_pair(r->first, timeout(r->second, ms, check_ms))); else return r; } catch (...) { th.request_interrupt(); th.join(); throw; } }); } #endif /** \brief Similar to interleave, but the heads are computed in parallel. Moreover, when pulling results from the lists, if one finishes before the other, then the other one is interrupted. */ #if !defined(LEAN_MULTI_THREAD) template lazy_list par(lazy_list const & l1, lazy_list const & l2, unsigned = g_small_sleep) { return interleave(l1, l2); } #else template lazy_list par(lazy_list const & l1, lazy_list const & l2, unsigned check_ms = g_small_sleep) { return mk_lazy_list([=]() { typename lazy_list::maybe_pair r1; typename lazy_list::maybe_pair r2; atomic done1(false); atomic done2(false); interruptible_thread th1([&]() { try { r1 = l1.pull(); } catch (...) { r1 = typename lazy_list::maybe_pair(); } done1 = true; }); interruptible_thread th2([&]() { try { r2 = l2.pull(); } catch (...) { r2 = typename lazy_list::maybe_pair(); } done2 = true; }); try { chrono::milliseconds small(check_ms); while (!done1 && !done2) { check_interrupted(); this_thread::sleep_for(small); } th1.request_interrupt(); th2.request_interrupt(); th1.join(); th2.join(); if (r1 && r2) { lazy_list tail(r2->first, par(r1->second, r2->second)); return some(mk_pair(r1->first, tail)); } else if (r1) { return some(mk_pair(r1->first, par(r1->second, l2))); } else if (r2) { return some(mk_pair(r2->first, par(l1, r2->second))); } else { return r2; } } catch (...) { th1.request_interrupt(); th2.request_interrupt(); th1.join(); th2.join(); throw; } }); } #endif }