Posts

CWEB

After a gap of about two decades(!) I decided to revisit composition in Knuth/Levy's CWEB on a C++ project. First, some technical notes: 1) I was unable to get the ctwill macros to work with my TeX system (despite the fact that it is all part of a single distribution). Admittedly, I didn't try too terribly hard, but it means that my output contains no mini-indexes or other ctwill features. 2) I write code using emacs, and this meant also adjusting to switching between cweb-mode and c++-mode. A number of functions I had written in emacs lisp were not portable directly to the new context, as they depended on file naming conventions. (If I were to take up use of CWEB more heavily, they would not be too difficult to modify - search backwards for @(...>= to determine the current output file name.) 3) My initial inclination was to use CWEB includes to generate one TeX file for the project. I abandoned this both because it ended up causing some size-related problems and because I t...

Considerations on an Optimization

An application – actually, successive pair of applications – is meant to process (in the end) about three hundred or so entries to convert a simple markup into complex LaTeX markup. It also enhances the data with information drawn from a flat-file text database (i.e. literally a set of .txt files in a single directory). This application was originally written in rust, which is not my favourite language, but one worth exercising to a degree. The rust application covered a limited subdomain, and generally avoided unsafe rust code, although in some cases it had to use cell mechanisms which converted compile-time to runtime safety. The initial iteration had the text database as one large file, with some logic allowing for extended seeking through chapters. It soon became apparent, after extending my dataset to about twenty or so items, that this was going to result in an unacceptable runtime — it was taking longer to process than the later TeX processing took, and although in theory this w...

Boundaries

Imagine that you have a well-designed application, possibly using dependency injection. It will almost always have a very thin layer visible in main() - capturing command-line parameters and environment variables, mainly[1], and then a call to an Application class which manages the rest of the initial logic - setting up resources such as logging, doing substantive validation of parameters, and creating and running the objects on the next level down which actually carry out the work of the application. This is simply following the SRP at a high level.

Convergence

This is an opinionated piece. Consider: fn handle_doubling_indicator(text: &str) -> (String, String) {     if text.len() == 7 { match &text[6..] {     "A" => ("First Mass".to_string(), text[0..5].to_string()),     "B" => ("Second Mass".to_string(), text[0..5].to_string()),     "C" =>  ("Third Mass".to_string(), text[0..5].to_string()),     "D" => ("Alternative observance".to_string(), text[0..5].to_string()),     "E" => ("Alternative propers".to_string(), text[0..5].to_string()),             _ => (String::new(), text.to_string()),         }     } else {         (String::new(), text.to_string())     } } and compare: std::pair<std::string, std::string> handle_doubling_indicator(const std::string &text) {   if (text.length() == 7)     {       switch (text[6]...

Small and Large Lambdas

Sometimes lambdas seem like a gift from heaven.  They allow the quick generation of STL algorithms without all the headache of setting up the structure of a unary or binary op class, and the ability to create general closures with a single extra character allows not having to worry about specific capturing of members by reference or pointer for use inside the function.  In C++14 and following the ability to define a stand-alone lambda with a given name allows for extra clarity when a given operation has to be executed several times but with a decidedly local context. (If I'm repeating the test-and-set pattern of  auto setImplementations = [&](std::string_view inVal) {     if (m_implementations.get() != nullptr)       throw std::runtime_error(           "Only one of -C, -d, -D, or -i allowed as type specification");     m_implementations = m_factory.create(inVal, m_names, m_dataRequirements);   }; four ti...

Injection versus Encapsulation

We (justly) hear a lot about dependency injection, and in many cases straightforward dependency injection, whether managed via a tool like Swing or just manually (as you have to do in C++) is clearly the best thing to do.  In most of these clear cases the dependency is public and shared: one common call will not only be economical in terms of code, but will allow the class into which the dependency is being injected to become less tightly coupled to its environment. But there are some interesting edge cases.  Excluding those cases where a strategy is purely private to a class -- that is, it is a resource exactly like one which might be injected, but it is properly an implementation detail  of that class and, if visible outside the class, is so purely to allow it to be tested independently[1] -- we have cases where although the resource is not unique to one class its implementation will be different in some way locally.

Generating Actions: A Small Design Discussion

Here's a mildly interesting small-scale design choice. I'm generating a set of actions corresponding to a set of states which are represented by monotonically-increasing enums. (And when I say generating, I mean it: this is actually code produced by a code generation tool I'm writing, not manually.) Each concrete instance implements the interface: class ITransmogrifierAction { public: virtual ~ITransmogrifierAction(); virtual TestEnum type() const = 0; virtual std::pair<Token,TestEnum> process(const TokenType& inVal) = 0; }; and the array being filled - std::array<std::unique_ptr<ITransmogrifierAction>, 5> m_actions; - will end up being a lookup for state machine transitions. The obvious STL way to do this is via generate_n. class Filler { public: std::unique_ptr<ITransmogrifierAction> operator()() { switch (m_n++) { case 0: return std::make_unique<AlphaTransmogrifierAction>(); case 1: retur...

State Machines

Almost any program can be modelled as a state machine. I say "almost any" because it is, just, possible to think of a program which cannot easily be resolved into anything more than a one-state state machine. Consider a command interpreter with no support for loops or function definitions (like the old DOS command.com) and with no internal commands, only the ability to execute external commands. It loops forever and does only one thing for every line it reads.    If it does no error handling, the execution is a single line, a call to execve or one of its relatives. There are internal stages inside the C library call, but they are effectively invisible to the program.  However, it's not a very useful program. Neither is any version of HelloWorld. The echo application does have its uses. Once one gets beyond this, even simple programs have at least two states: setup and execute. A simple version of cp has to open two files for input and output before beginning to copy data....

Some Thoughts on Interfaces

  One principle that I have seen enunciated is that one should not have an interface with only one implementation. This is generally sensible. Why separate out an interface if there is no variation? You can get physical insulation without an interface, if you need one, using the PIMPL idiom. (This approach explicitly rejects the idea of creating an interface/implementation pair just because you expect a need to add another implementation in three months. Just be aware that coding in that context needs to be careful - the first time you create a class which owns an instance of the class with potential variation, and it doesn't do so through a pointer of some sort, you're adding more work than is necessary down the line when you suddenly need to be holding a pointer to an interface. My own view is that if you really know that a second implementation is barrelling down the tracks towards you, there's no reason not to put the eventually-required separation of responsibilities ...