Реализация Haskell's Maybe Monad в С++ 11

Я пытаюсь реализовать монаду Maybe из Haskell, используя лямбда-функции в С++ 11 и шаблоны. Вот что у меня есть до сих пор

#include<functional>
#include<iostream>
using namespace std;

template<typename T1>
struct Maybe
{
  T1 data;
  bool valid;
};

template<typename T1, typename T2>
Maybe<T2> operator>>=(Maybe<T1> t, std::function < Maybe<T2> (T1)> &f)
{
  Maybe<T2> return_value;
  if(t.valid == false)
  {        
    return_value.valid = false;
    return return_value;
  }
  else
  {        
    return f(t.data);
  }            
}


int main()
{
  Maybe<int> x = {5, true};
  Maybe<int> y = {29, false};

  auto z = [](int a) -> Maybe<int>
    {
      Maybe<int> s;
      s.data = a+1;
      s.valid = true;
      return s;
    };

  Maybe<int> p = (x >>= z);
  Maybe<int> q = (y >>= z);

  cout<<p.data<<' '<<p.valid<<endl;        
  cout<<q.data<<' '<<q.valid<<endl;
}    

Когда дело доходит до фактического вызова >>=, я получаю сообщение об ошибке компилятора о том, что для оператора >>= не найдено совпадений. Подводит ли меня мое понимание лямбда-функций С++ 11?


person rpg    schedule 13.03.2012    source источник
comment
Знаете ли вы Boost.Optional ? Это монада Maybe C++.   -  person Xeo    schedule 14.03.2012
comment
Я делаю. Я пытался реализовать свои собственные, чтобы немного понять монады.   -  person rpg    schedule 14.03.2012
comment
@rpg: С++ не очень удобен для монад.   -  person Cat Plus Plus    schedule 14.03.2012
comment
>>= в C++ имеет неправильную ассоциативность. Вы лучше поймете монады, если вам не придется биться головой о совершенно не связанные между собой стены в процессе. Попробуйте изучить монады в Haskell.   -  person R. Martinho Fernandes    schedule 14.03.2012
comment
@CatPlusPlus И что? Метапрограммирование шаблонов тоже не для слабонервных. И никто не говорил, что результат должен быть красивым. Если это даст OP лучшее понимание монад: миссия выполнена.   -  person    schedule 14.03.2012
comment
@cat plus plus: я это понимаю. Вопрос в том, есть ли способ заставить этот подход работать. Типы, задействованные в лямбде, кажутся прекрасными.   -  person rpg    schedule 14.03.2012
comment
@ R. Martinho Fernandes: Haskell, конечно, лучший язык для изучения монад, но я лучше понимаю C++. Я пробую это, чтобы понять, как писать монадический код и как эта концепция работает на практике.   -  person rpg    schedule 14.03.2012
comment
Обратите внимание, что >>= не имеет ничего общего с монадами. Это оператор, используемый в Haskell для выражения операции привязки монад, но это просто решение, принятое разработчиками языка Haskell. Использование этого конкретного оператора не является неотъемлемой частью монады. Так что нет смысла пытаться реализовать монады на C++ с синтаксисом Haskell. Реализуйте монады на C++ удобным для C++ способом, вместо того, чтобы слепо копировать совершенно произвольные проектные решения, принятые в Haskell.   -  person jalf    schedule 14.03.2012
comment
Ну конечно, сделай это для науки. Но имейте в виду, что без замены нотации do то, как это работает на практике... не очень практично. По крайней мере, пожалуйста, пересмотрите использование >>=: m >>= f >>= g в C++ — это m >>= (f >>= g), а в Haskell — (m >>= f) >>= g.   -  person R. Martinho Fernandes    schedule 14.03.2012
comment
Я использовал ››= просто по привычке. Вероятно, это плохой выбор для C++.   -  person rpg    schedule 14.03.2012
comment
>>= произносится как bind, так что я предлагаю вам использовать его. Кроме того, поскольку return является ключевым словом C++, вы можете позаимствовать имя unit из теории категорий.   -  person Vitus    schedule 14.03.2012
comment
@ Витус нет, это не так. Произносится как bind на языке Haskell. В C++ это произносится как оператор присваивания сдвига вправо или что-то в этом роде. Это была именно моя точка зрения. Если вы хотите реализовать монаду, вам нужна операция bind. Но операция bind не обязательно должна быть представлена ​​оператором >>=. Эта ассоциация существует только в Haskell, это не универсальное требование или что-то, что вы должны иметь, чтобы что-то было монадой.   -  person jalf    schedule 14.03.2012
comment
У Бартоша есть хороший пост в блоге на эту тему: bartoszmilewski.com/2011/07 /11/монады-в-с   -  person mavam    schedule 14.03.2012
comment
@jalf: Конечно; перегрузка >>= в C++ не очень удачная идея, поэтому я предложил лучшее название для функции (bind происходит от >>= в Haskell).   -  person Vitus    schedule 16.03.2012
comment
stackoverflow.com/questions/7690864 / Взгляните на это   -  person zrb    schedule 18.09.2014


Ответы (6)


Тип лямбды не является специализацией std::function. Это какой-то безымянный тип. Существует преобразование в std::function, но это означает, что для него не будет работать вывод типа. Итак, в этом вызове:

Maybe<int> p = (x >>= z);

Тип T2 не может быть выведен:

Maybe<T2> operator>>=(Maybe<T1> t, std::function < Maybe<T2> (T1)> &f)

Сохраните лямбду в переменной std::function с самого начала, и она должна работать:

std::function < Maybe<int> (int)> z = [](int a) -> Maybe<int> { ... };

Однако, вероятно, проще принять любой объект функции. Таким образом, вы все еще можете использовать auto для лямбды.

template<typename T1, typename F>
typename std::result_of<F(T1)>::type
operator>>=(Maybe<T1> t, F&& f) {
    ... std::forward<F>(f)(t.data);
}
person R. Martinho Fernandes    schedule 13.03.2012
comment
Если типы для T2 не могут быть выведены, значит ли это, что этот подход обречен? Есть ли другой способ взломать его без использования Boost. Я хотел бы видеть весь код в одном месте, чтобы лучше его понимать, поэтому я не хочу использовать Boost. - person rpg; 14.03.2012
comment
Большое спасибо. Это сработало. Но удаление auto сделало код намного более подробным. Какой вид делает это непрактичным для использования. :П - person rpg; 14.03.2012
comment
Последний вариант работает с auto, потому что он не требует преобразования: он выводит F к любому безымянному типу лямбды (плюс соответствующий вид ссылки). - person R. Martinho Fernandes; 14.03.2012

У меня работает следующее: я использую decltype для вывода типа, возвращаемого лямбдой:

template<typename T1, typename Func>    
auto operator>>=(Maybe<T1> t, Func f) -> decltype(f(t.data))
{    
  decltype(f(t.data)) return_value;    
  if(t.valid == false)    
  {            
    return_value.valid = false;    
    return return_value;    
  }    
  else    
  {            
    return f(t.data);    
  }                
}

ИЗМЕНИТЬ

Для безопасности типов:

template<typename T1>    
struct Maybe    
{    
  T1 data;    
  bool valid;

  static const bool isMaybe = true;
};

template<typename T1, typename Func>     
auto operator>>=(Maybe<T1> t, Func f) -> decltype(f(t.data)) 
{
  typedef decltype(f(t.data)) RT;
  static_assert(RT::isMaybe, "F doesn't return a maybe");
  ...
person J.N.    schedule 13.03.2012
comment
Спасибо. Это тоже работает. Но, насколько я могу судить, он немного теряет безопасность типов. Думаю, меня это не должно сильно волновать, учитывая, что это C++. - person rpg; 14.03.2012
comment
Не так много безопасности типов теряется, и вы получаете гибкость. Таким образом, вы не можете передавать функции, которые не принимают неправильные аргументы, они могут просто возвращать результаты, которые не являются «Может быть», что означает, что вы можете преобразовать «Может быть» в нормальный тип, если хотите. - person J.N.; 14.03.2012

Вот моя, возможно, "монада", которую я довольно часто использую в своих проектах на C++ (отказ от ответственности: см. комментарии ниже). Это больше похоже на Haskell Maybe, чем на вашу реализацию, поскольку он содержит объект только в случае just (указывает на него mobj), не теряя места, если это nothing. Это также позволяет использовать семантику перемещения C++11, чтобы избежать ненужных копий. Типы возвращаемых значений fmap (функция-член fmapped) и >>= выводятся с помощью decltype.

template<typename DataT>
class maybe;
template<typename DataT>
maybe<DataT> just(const DataT &obj);
struct nothing_object{nothing_object(){}};
const nothing_object nothing;

                 //template class objects of which may or may not contain some given
                // data object. Inspired by Haskell's Maybe monad.
template<typename DataT>
class maybe {
  DataT *obj;

 public:

  class iterator {
    DataT *mobj;
    explicit iterator(DataT *init):mobj(init){}
   public:
    iterator():mobj(nullptr){}
    iterator(const iterator &cp):mobj(cp.mobj){}
    bool operator!=(const iterator &other)const{return mobj!=other.mobj;}
    DataT &operator*() const{return *mobj;}
    iterator &operator++(){ mobj=nullptr; return *this; }
    friend class maybe;
  };
  class const_iterator {
    const DataT *mobj;
    explicit const_iterator(const DataT *init):mobj(init){}
   public:
    const_iterator():mobj(nullptr){}
    const_iterator(const const_iterator &cp):mobj(cp.mobj){}
    bool operator!=(const const_iterator &other)const{return mobj!=other.mobj;}
    const DataT &operator*() const{return *mobj;}
    const_iterator &operator++(){ mobj=nullptr; return *this; }
    friend class maybe;
  };
  iterator begin(){return iterator(obj);}
  iterator end(){return iterator();}
  const_iterator begin()const{return const_iterator(obj);}
  const_iterator end()const{return const_iterator();}
  const_iterator c_begin()const{return const_iterator(obj);}
  const_iterator c_end()const{return const_iterator();}

  bool is_nothing()const{return obj==nullptr;}
  void make_nothing(){delete obj; obj=nullptr;}
  bool is_just()const{return obj!=nullptr;}
  template<typename CpDataT>
  void with_just_assign(CpDataT &mdftg)const{if(obj) mdftg=*obj;}
  DataT &from_just(){return *obj;}
  DataT &operator*(){return *obj;}
  const DataT &from_just()const{return *obj;}
  const DataT &operator*()const{return *obj;}

  template<typename CmpDataT>
  bool operator==(const maybe<CmpDataT> &cmp)const{
    return is_just()==cmp.is_just() && (is_nothing() || *obj==*cmp.obj); }
  template<typename CmpDataT>
  bool operator!=(const maybe<CmpDataT> &cmp)const{
    return is_just()!=cmp.is_just() || (is_just() && *obj!=*cmp.obj); }
  bool operator==(const nothing_object &n)const{return obj==nullptr;}
  bool operator!=(const nothing_object &n)const{return obj!=nullptr;}

  template<typename MpFnT>
  auto fmapped(MpFnT f) const -> maybe<decltype(f(*obj))> {
    return obj? just(f(*obj)) : nothing;                  }
  template<typename MonadicFn>
  auto operator>>=(MonadicFn f) const -> decltype(f(*obj)) {
    return obj? f(*obj) : nothing;                         }
  template<typename ReplaceDT>
  auto operator>>(const maybe<ReplaceDT> &r) const -> maybe<ReplaceDT> {
    return obj? r : nothing;                                           }
  auto operator>>(const nothing_object &n) const -> maybe<DataT> {
    return nothing;                                              }


  maybe(const nothing_object &n):obj(nullptr){}
  template<typename CpDataT>
  explicit maybe(const CpDataT &cobj):obj(new DataT(cobj)){}
  template<typename CpDataT>
  maybe &operator=(const CpDataT &cobj){delete obj; obj=new DataT(cobj); return *this;}
  template<typename CpDataT>
  maybe(const maybe<CpDataT> &cp):obj(cp.is_just()?new DataT(cp.from_just()):nullptr){}
  template<typename CpDataT>
  maybe &operator=(const maybe<CpDataT> &cp){
    delete obj;  obj = cp.is_just()? new DataT(cp.from_just()) : nullptr; return *this;}
  maybe(maybe<DataT> &&mv):obj(mv.obj){mv.obj=nullptr;}
  maybe &operator=(maybe<DataT> &&mv) {
    delete obj; obj=mv.obj; mv.obj=nullptr; return *this; }

  ~maybe(){delete obj;}
};

template<typename DataT>
auto just(const DataT &obj) -> maybe<DataT> {return maybe<DataT>(obj);}

template<typename MpFnT, typename DataT>              // represents Haskell's <$> infix
auto operator^(MpFnT f, const maybe<DataT> &m) -> decltype(m.fmapped(f)) {
  return m.fmapped(f);
}

template<typename DataT>
auto joined(const maybe<maybe<DataT>> &m) -> maybe<DataT> {
  return m.is_just()? m.from_just() : nothing;
}


template<typename DataT>
auto maybe_yes(const std::pair<DataT,bool>& mbcst) -> maybe<DataT> {
  return mbcst.second ? just(mbcst.first) : nothing;
}
template<typename DataT>
auto maybe_not(const std::pair<DataT,bool>& mbcst) -> maybe<DataT> {
  return !mbcst.second ? just(mbcst.first) : nothing;
}

Несколько странно выглядящие итераторы begin и end позволяют использовать его в циклах for на основе диапазона C++11:

maybe<int> a = just(7), b = nothing;

for (auto&i: a) std::cout << i;
for (auto&i: b) std::cout << i;

выводит только один раз 7.

person leftaroundabout    schedule 13.03.2012
comment
+1 за интересный код, но его можно было бы представить немного лучше. - person J.N.; 14.03.2012
comment
Вы не заметили, что неторговое пространство происходит за счет (не пренебрегаемого) расхода динамического распределения памяти. Есть ли причина не использовать unique_ptr ? - person J.N.; 14.03.2012
comment
@Дж.Н. действительно, динамическое выделение памяти намного дороже, чем стек, поэтому использовать его для множества небольших объектов — плохая идея. Я не утверждаю, что это оптимальная реализация, у меня просто возникла идея реализовать ее (для класса, в котором было довольно много элементов данных, поэтому было хорошо выделить в куче), когда я начал изучать C++ 0x и хотел узнать о реализации семантики перемещения. И сначала я хотел знать, как это работает с простыми указателями, теперь я бы использовал unique_ptr, возможно, лучший выбор здесь. - person leftaroundabout; 14.03.2012

Заметили, что std::function имеет пустое состояние, у нас может быть следующая реализация

template<typename T>
class Maybe{
private:
    Maybe(T t){
        get = [t](){ return t; };
    }
    Maybe(){}
    std::function<T ()> get;
public:
    typedef T content_type;

    template<typename WhenJust, typename WhenNothing>
    auto on(WhenJust &&whenJust, WhenNothing &&whenNothing) 
        -> decltype(whenNothing()){
        if(get==nullptr) return whenNothing();
        else return whenJust(get());
    }
    template<typename U>
    friend Maybe<U> just(U u);
    template<typename U>
    friend Maybe<U> nothing();
};

template<typename T>
Maybe<T> just(T t){
    return Maybe<T>(t);
}

template<typename T>
Maybe<T> nothing(){
    return Maybe<T>();
}

template<typename T, typename BinderFunction>
auto operator >>(Maybe<T> m, BinderFunction bind) 
    -> Maybe<typename decltype(bind(*((T*)nullptr)))::content_type> {
    return m.on([bind](T v){
        return bind(v);
    },[](){
        return nothing<typename decltype(bind(*((T*)nullptr)))::content_type>();
    });
}

В этой реализации все фабричные методы являются бесплатными (дружескими) функциями, оператор >> (не путать с >> в Haskell, это эквивалент >>= с той же ассоциативностью) также свободен и даже не является дружественной функцией. Также обратите внимание на функцию-член on, которая используется для того, чтобы любой клиент, предназначенный для использования экземпляра Maybe, должен быть подготовлен для обоих случаев (просто или ничего).

Вот пример использования:

int main()
{
    auto n = just(10) >> [](int j){ std::cout<<j<<" >> "; return just(j+10.5); }
        >> [](double d){ std::cout<<d<<" >> "; return nothing<char>(); }
        >> [](char c){ std::cout<<c; return just(10); }
        ;

    n.on(
        [](int i) { std::cout<<i; },
        []() { std::cout<<"nothing!"; });

    std::cout << std::endl;
    return 0;
}

Выход

10 >> 20.5 >> nothing!
person Earth Engine    schedule 26.07.2013

Мои 5 кт.

Пример использования:

Maybe<string> m1 ("longlonglong");

auto res1 = m1 | lengthy  | length;

lengthy и length являются "монадическими лямбда-выражениями", т.е.

auto length = [] (const string & s) -> Maybe<int>{ return Maybe<int> (s.length()); };

Полный код:

// g++ -std=c++1y answer.cpp

#include <iostream>
using namespace std;

// ..................................................
// begin LIBRARY
// ..................................................
template<typename T>
class Maybe {
  // 
  //  note: move semantics
  //  (boxed value is never duplicated)
  // 

private:

  bool is_nothing = false;

public:
  T value;

  using boxed_type = T;

  bool isNothing() const { return is_nothing; }

  explicit Maybe () : is_nothing(true) { } // create nothing

  // 
  //  naked values
  // 
  explicit Maybe (T && a) : value(std::move(a)), is_nothing(false) { }

  explicit Maybe (T & a) : value(std::move(a)), is_nothing(false) { }

  // 
  //  boxed values
  // 
  Maybe (Maybe & b) : value(std::move(b.value)), is_nothing(b.is_nothing) { b.is_nothing = true; }

  Maybe (Maybe && b) : value(std::move(b.value)), is_nothing(b.is_nothing) { b.is_nothing = true; }

  Maybe & operator = (Maybe & b) {
    value = std::move(b.value);
    (*this).is_nothing = b.is_nothing;
    b.is_nothing = true;
    return (*this);
  }
}; // class

// ..................................................
template<typename IT, typename F>
auto operator | (Maybe<IT> mi, F f)  // chaining (better with | to avoid parentheses)
{
  // deduce the type of the monad being returned ...
  IT aux;
  using OutMonadType = decltype( f(aux) );
  using OT = typename OutMonadType::boxed_type;

  // just to declare a nothing to return
  Maybe<OT> nothing;

  if (mi.isNothing()) {
    return nothing;
  }

  return f ( mi.value );
} // ()

// ..................................................
template<typename MO>
void showMonad (MO m) {
  if ( m.isNothing() ) {
    cout << " nothing " << endl;
  } else {
    cout << " something : ";
    cout << m.value << endl;
  }
}

// ..................................................
// end LIBRARY
// ..................................................

// ..................................................
int main () {

  auto lengthy = [] (const string & s) -> Maybe<string> { 
    string copyS = s;
    if  (s.length()>8) {
      return Maybe<string> (copyS);
    }
    return Maybe<string> (); // nothing
  };

  auto length = [] (const string & s) -> Maybe<int>{ return Maybe<int> (s.length()); };

  Maybe<string> m1 ("longlonglong");
  Maybe<string> m2 ("short");

  auto res1 = m1 | lengthy  | length;

  auto res2 = m2 | lengthy  | length;

  showMonad (res1);
  showMonad (res2);


} // ()
person cibercitizen1    schedule 22.01.2015

Буквально копировать и вставлять из стиль Haskell Возможно тип и *цепочка* в С++ 11

Это, вероятно, то, чего вы действительно хотите достичь

#include <iostream>
#include <map>
#include <deque>
#include <algorithm>
#include <type_traits>

typedef long long int int64;

namespace monad { namespace maybe {

  struct Nothing {};

  template < typename T >
  struct Maybe {
    template < typename U, typename Enable = void >
    struct ValueType {
      typedef U * const type;
    };

    template < typename U >
    struct ValueType < U, typename std::enable_if < std::is_reference < U >::value >::type > {
      typedef typename std::remove_reference < T >::type * const type;
    };

    typedef typename ValueType < T >::type value_type;

    value_type m_v;

    Maybe(Nothing const &) : m_v(0) {}

    struct Just {
      value_type m_v;
      Just() = delete;
      explicit Just(T &v) : m_v(&v) {
      }
    };

    Maybe(Just const &just) : m_v(just.m_v) {
    }
  };

  Nothing nothing() {
    return Nothing();
  }

  template < typename T >
  Maybe < T > just(T &v) {
    return typename Maybe < T >::Just(v);
  }

  template < typename T >
  Maybe < T const > just(T const &v) {
    return typename Maybe < T const >::Just(v);
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, R (*f)(A const &)) {
    if (t.m_v)
      return just < R >(f(*t.m_v));
    else
      return nothing();
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, Maybe < R > (*f)(A const &)) {
    if (t.m_v)
      return f(*t.m_v);
    else
      return nothing();
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, R (*f)(A &)) {
    if (t.m_v)
      return just < R >(f(*t.m_v));
    else
      return nothing();
  }

  template < typename T, typename R, typename A >
  Maybe < R > operator | (Maybe < T > const &t, Maybe < R > (*f)(A &)) {
    if (t.m_v)
      return f(*t.m_v);
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, R (T::*f)(A const &...) const) {
    if (t.m_v)
      return just < R >(((*t.m_v).*f)());
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, Maybe < R > (T::*f)(A const &...) const) {
    if (t.m_v)
      return just < R >((t.m_v->*f)());
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, R (T::*f)(A const &...)) {
    if (t.m_v)
      return just < R >(((*t.m_v).*f)());
    else
      return nothing();
  }

  template < typename T, typename R, typename... A >
  Maybe < R > operator | (Maybe < T const > const &t, Maybe < R > (T::*f)(A const &...)) {
    if (t.m_v)
      return just < R >((t.m_v->*f)());
    else
      return nothing();
  }

  template < typename T, typename A >
  void operator | (Maybe < T > const &t, void (*f)(A const &)) {
    if (t.m_v)
      f(*t.m_v);
  }

}}

struct Account {
  std::string const m_id;
  enum Type { CHECKING, SAVINGS } m_type;
  int64 m_balance;
  int64 withdraw(int64 const amt) {
    if (m_balance < amt)
      m_balance -= amt;
    return m_balance;
  }

  std::string const &getId() const {
    return m_id;
  }
};

std::ostream &operator << (std::ostream &os, Account const &acct) {
  os << "{" << acct.m_id << ", "
 << (acct.m_type == Account::CHECKING ? "Checking" : "Savings")
 << ", " << acct.m_balance << "}";
}

struct Customer {
  std::string const m_id;
  std::deque < Account > const m_accounts;
};

typedef std::map < std::string, Customer > Customers;

using namespace monad::maybe;

Maybe < Customer const > getCustomer(Customers const &customers, std::string const &id) {
  auto customer = customers.find(id);
  if (customer == customers.end())
    return nothing();
  else
    return just(customer->second);
};

Maybe < Account const > getAccountByType(Customer const &customer, Account::Type const type) {
  auto const &accounts = customer.m_accounts;
  auto account = std::find_if(accounts.begin(), accounts.end(), [type](Account const &account) -> bool { return account.m_type == type; });
  if (account == accounts.end())
    return nothing();
  else
    return just(*account);
}

Maybe < Account const > getCheckingAccount(Customer const &customer) {
  return getAccountByType(customer, Account::CHECKING);
};

Maybe < Account const > getSavingsAccount(Customer const &customer) {
  return getAccountByType(customer, Account::SAVINGS);
};

int64 const &getBalance(Account const &acct) {
  return acct.m_balance;
}

template < typename T >
void print(T const &v) {
  std::cout << v << std::endl;
}

int main(int const argc, char const * const argv[]) {
  Customers customers = {
    { "12345", { "12345", { { "12345000", Account::CHECKING, 20000 }, { "12345001", Account::SAVINGS, 117000 } } } }
  , { "12346", { "12346", { { "12346000", Account::SAVINGS, 1000000 } } } }
  };

  getCustomer(customers, "12346") | getCheckingAccount | getBalance | &print < int64 const >;
  getCustomer(customers, "12345") | getCheckingAccount | getBalance | &print < int64 const >;
  getCustomer(customers, "12345") | getSavingsAccount | &Account::getId | &print < std::string const >;
  //  getCustomer(customers, "12345") | getSavingsAccount | [](Account &acct){ return acct.withdraw(100); } | &print < std::string const >;
}
person zrb    schedule 18.09.2014