[Bro-Dev] Rethinking Broker's blocking API

Robin Sommer robin at icir.org
Fri Jan 6 13:44:19 PST 2017


I'm going back and forth between the two versions. I think I'd take
the 2nd (the custom class), though maybe with the API I had used in my
example instead (i.e, msg.failed() and then a single status() method
for both cases) as having two methods error/status returning the same
thing looks a bit odd. But not a big deal either way, any of these
options sounds fine to me.

Robin

On Fri, Jan 06, 2017 at 10:32 -0800, you wrote:

> > I think the name "error" is not just misleading but would also turn
> > out tricky to use correctly. 
> 
> Agreed.
> 
> >     auto msg = ep.receive();
> > 
> >     if (msg)
> >         return f(*msg); // unbox contained message
> > 
> >     if (msg.failed())
> >         cout << "Trouble: " << to_string(msg.status()) << endl;
> >     else
> >         cout << "Status change: " << to_string(msg.status()) << endl;
> > 
> > In either case one could then also use a switch to differentiate the
> > status() further.
> 
> I think this is semantically what we want. Because broker::error is just
> a type alias for caf::error (and broker::expected just caf::expected),
> it's currently not possible to change the API of those classes. I see
> two solutions, one based on the existing vehicles and one that
> introduces a new structure with three states for T, error, and status.
> 
> Here's the first. A caf::error class has three interesting member
> functions:
> 
>   class error {
>     uint8_t code();
>     atom_value category();
>     const message& context();
>   };
> 
> The member category() returns the type of error. In Broker, this is
> always "broker". CAF errors have "caf" as category. We could simply
> split the "broker" error category into "broker-status" and
> "broker-error" to distinguish the error class. We would keep the two
> different enums and provide free functions for a user-friendly API.
> Example:
> 
>   // -- Usage -------------------------------------------
> 
>   auto msg = ep.receive(); // expected<message>
> 
>   if (msg) {
>     f(*msg); // unbox message
>   } else if (is_error(msg)) {
>     cout << "error: " << to_string(msg.error()) << endl;
>     if (msg == error::type_clash)
>       // dispatch concrete error
>   } else if (is_status(msg)) {
>     cout << "status: " << to_string(msg.error()) << endl;
>     if (msg == status::peer_added)
>       // dispatch concrete status
>   } else {
>     // CAF error
>   }
> 
>   // -- Broker implementation ---------------------------
> 
>   using failure = caf::error;
> 
>   enum status : uint8_t { /* define status codes * /};
> 
>   enum error : uint8_t { /* define error codes */ };
> 
>   bool is_error(const failure& f) { 
>     return f.category() == atom("broker-error");
>   }
> 
>   bool is_status(const failure& f) { 
>     return f.category() == atom("broker-status");
>   }
> 
>   template <class T>
>   bool failed(const expected<T>& x) { 
>     return !x && failed(x.error());
>   }
> 
>   template <class T>
>   bool operator==(const expected<T>& x, status s) {
>     return !x && x.error() == s;
>   }
> 
> The only downside here is that we're still calling msg.error() in the
> case of a status. That's where the second option comes in. Let's call it
> result<T> for now (it won't matter much, because most people will use it
> with "auto"). A result<T> wraps an expected<T> and provides a nicer API
> to distinguish errors and status more cleanly. Example:
> 
>   // -- Usage -------------------------------------------
> 
>   auto msg = ep.receive(); // result<message>
> 
>   if (msg) {
>     f(*msg); // unbox T
>   } else if (auto e = msg.error()) {
>     cout << "error: " << to_string(*e) << endl;
>     if (*e == error::type_clash)
>       // dispatch concrete error
>   } else if (auto s = msg.status()) {
>     cout << "status: " << to_string(*s) << endl;
>     if (*s == status::peer_added)
>       // dispatch concrete status
>   } else {
>     assert(!"not possible");
>   }
> 
>   // -- Broker implementation ---------------------------
> 
>   enum status : uint8_t { /* define status codes * /};
> 
>   enum error : uint8_t { /* define error codes */ };
> 
>   template <class T>
>   class result {
>   public:
>     optional<caf::error> status() const;
>     optional<caf::error> error() const;
> 
>     // Box semantics
>     explicit operator bool() const;
>     T& operator*();
>     const T& operator*() const;
>     T* operator->();
>     const T* operator->() const;
> 
>   private:
>     expected<T> x_;
>   };
> 
> Thoughts?
> 
>     Matthias
> 


-- 
Robin Sommer * ICSI/LBNL * robin at icir.org * www.icir.org/robin


More information about the bro-dev mailing list