2022-07-30

Why didn't I think of that description?

For the last couple of weeks I've been making my way through A Brief History of the Earth (subtitle "Four Billion Years in Eight Chapters") by Andrew H. Knoll. Brief shout-out to the author for including the (anti-)neutrino among the products associated with beta decay in his discussion of radiometric dating.

Nice book so far (I've reached the last chapter). I had a rough appreciation for much of the subject matter coming in, but it is clear that there has been much progress since the last time I read up on the discipline. Some open questions have been answered outright, and many details have come into focus where rough outlines existed before. All very cool.

One detail of the writing, however, is worthy of special mention. The bibliography for each chapter is divided into two sections: "Approachable Readings" and "More Technical References". I sorely wish I'd thought to use "approachable" as a description of pop-sci material more often in the past.

2022-07-25

2022-07-11

Little surprises #8: using the preprocessor to create latent bugs

Aside: As opposed to using the build system or the template processor which are totaly different except in the ways they are completely the same.


Languages with C-derived syntax and exceptions generally have a try-catch construct and the ones I'm familiar with have a little trap for the inattentive programmer: the catch block is a different scope from the try blocks. Which means when you merrily write try { ErrorInfo e; someRiskyOperation(e) } catch (...) { print("Risky operation failed with error %s\n",e.explanation()); } because the argument to someRiskyOperation is an out-param1 and you hope to capture the error data that was generated before the exception was thrown, you actually get a compilation error because e isn't in scope when you try to issue the message.

Not a big problem, of course, at least two work-around are obvious.

Except that I tried to send a message to our logger, so the code looked like try { ErrorInfo e; someRiskyOperation(e) } catch (...) { LOG_WARN("Risky operation failed with error " + e.explanation()); } where LOG_WARN is a macro whose purpose is to capture the file and line where the error occured.2 And that's a problem if this code occurs in a library that can be compiled with logging support turned off by simply defining the logging macros as nulls: #define LOG_WARN(explanation) , because in that case the compiler never sees the offending e.explanation. It was rewritten as nothing even before lexing. So you can test the code with logging turned off to your hearts content and never find the bug. Sigh.


1 Yes, I agree: out-params are generally evil. It's a legacy API. Perhaps we'll get around to replacing it eventually.

2 When it actually does something it looks something like #define LOG_WARN(explanation) do { \ logger:Message logmsg(__FILE__, __LINE__, explanation, logger::Warning); \ logger::getLogHandler().post(logmsg); \ while(false) and it can't be a function because __FILE__ and __LINE__ are macros and have to be evaluated in place. Unless and until the language provides operators for capturing source position data like that this is the best we can do.

2022-07-10

The Rip-off from Cleveland Depths.

Title in reference to Fritz Leiber's 1962 novella "The Creature from Cleveland Depths".1

The complex of product options that go by "activity tracker", "health monitor", or "wellness monitor" (and smart watches that include that suite of functionality) aren't exactly the "tickler"s in the story, but they share a lot of DNA with them. They are, after all, automated systems we wear explicitly so they can nudge us toward better versions of ourselves. You can get other parts of the tickler package as apps. Either for your phone or, if you choose the right smart watch for the wrist mounted device itself.2

One of the psychologically interesting things is that the prime statistics these things recorded when they first took off was ... steps. They were glorified, electronic pedomenters.3 Pedometers have been around for ages and never made such a big splash before. I suppose gamification is the key to that. Anyway, today's rantlet is about the way a wrist mounted pedometer fails us: you don't get credit for pushing a shopping cart. I can trek up and down every aisle of the local, big-box, warehouse club and the fool machine registers a few hundred steps because pushing the fool cart more or less immobilizes my wrist. I only get credit for putting things in the cart. The old waist-mounted mechanical jobs managed that kind of use better.

Okay, so it's seriously a first-world problem. You're right. And it's of no actual consequence. You're right about that, too. It still annoys me.


1 Available from Project Gutenberg or in audio form from Librivox.

2 I wear a Withing ScanWatch so I can't get a bunch of apps on my wrist, but I do get reasonably comprehensive health tracking, weeks of battery life (I only every charge it while I shower), and the aesthetic appeals to me.

3 Indeed, the cheapest ones still are, but many do a lot more these days.

2022-07-03

Who shaves the barber?

Or how should a logging framework appraise the caller of unexpected situations caught and handled, anyway?

2022-07-02

The ABCs of aspect oriented C++ (part1)

OK, so the title is a little ambitious, but I am going to show you how to prove a limited level of aspect orientations for a few cases.

What the heck is "aspect oriented programming", anyway?

Honestly, I'm not strong on the subject never have worked in that format myself, but the short version is that it allows you to add behavior to program abstraction without editing the associated code. Common examples are attaching entry and exit loggers or call count tracker to functions without editing the function. C++ has no built in support for it.

Anyway, I'm going to start by showing you a very limited and specific trick that has the same flavor, then generalize it.

Example: interface with non-trivial behavior

We have a hand-rolled serialization mechanism we use on a work project. It supports selected fundamental and standard-library type and provides an interface (AKA Abstract Base Class) for building support into classes we write for ourselves1

class SerialInterface { public: SerialInterface1() = default; ~SerialInterface1() = default; virtual bool serialize(std::ostream & out) const = 0; virtual bool deserialize(std::istream & in) = 0; };

Class we want to (de)serialize derive from SerialInterface and implement the virtual methods using some utility code to maintain uniform behavior.2 The return values tell you if the process was successful or not.

We have classes whose sole purposes is to be serialized and deserialized to support interprocess data transfer, which makes it very convenient to accept the input stream in a constructor

class Client: public SerialInterface1 { public: Client(std::istream & serialData) : Client() { deserialize(serialData); } };

But what happened to the status? It's lost, which is a problem.

Now, we could throw in the the event deserialize returns failure, but who like exceptions? An alternative would be to cache the success value and offer an interface to query the value. But then you have to duplicate the code in every client class. What we would like is to give the base class the status caching behavior without needing any duplicated code in the client classes. We achieve this by splitting the implementation of serialize and deserialize into a wrapper defined in the base class and a code implementation defined by the clients.

class SerialInterface2 { public: SerialInterface2() = default; ~SerialInterface2() = default; bool status() const { return _serializeImpl; } bool serialize(std::ostream & out) const { _serializationStatus = serializeImpl(out); return status(); } bool deserialize(std::istream & in) { _serializationStatus = deserializeImpl(out); return status(); } private: virtual bool serializeImpl(std::ostream & out) const = 0; virtual bool deserializeImpl(std::istream & in) = 0; bool _serializationStatus = true; };

Now all client class support status inquires without needing any code to support it. We've provided a status aspect to all the clients.

More general aspects for callables

It is very common for the examples I see in explanations of AOP to focus on providing aspects to callable subprograms (functions, procedures, whatever you want to call them). And that is interesting because modern C++ already has a highly general idea of "a thing you can call like a function", which means that we can try to use the approach from SerialInterface2 to provide aspects to callables.

To generalize the behavior we provide virtual implementations for code to be run both before and after the the callable.

class FunctionAspect { public: using function_t = bool(int, double&, const std::string &); FunctionAspect() = delete; FunctionAspect(std::function & func): _callable(func) {} bool operator()(int i, double & d, const std::string & s); private: virtual void prologue() = 0; virtual void epilogue() = 0; std::function _callable; }; bool FunctionAspect::operator()(int i, double & d, const std::string & s) { prologue(); bool result = _callable.operator()(i,d,s); epilogue(); return result; }

Concrete derivative classes can then provide arbitrary code wrappers around any std::function with the appropriate signature. That restriction is, of course, a major drawback, but we're on the road to general purpose aspects.


1 Come to think of it, we could have avoided exactly this complexity if we had access to a general support for AOP in C++ in the first place.

2 Yeah, we should probably have used JSON, but it works.