2026-02-02

Hey, ya wanna help?

Here's the thing about smart phones: you cannot reasonably prop them between your shoulder and you ear. Not only will you get an instant muscle cramp (and probably scoliosis within minutes if you persisted), but the thing won't actually stay there. In that respect the really, deeply suck.

But whatever. Price you pay for the benefits of the form factor. Or whatever.

That said, this has a consequence: if someone calls and (a) you don't feel you can skip it, (b) you still need to have both hands for something (anything) other than the phone, and (c) you don't currently have your buds in then you must, in short order:

  1. answer the call
  2. switch to speaker
  3. prop the phone somewhere

Presumably the people who write the UI for these things have this experience, too.

But recently with my phone, when I tap to answer, the UI goes through some flashy, batter draining, nonsensical animation which results in the hang-up control landing right where the change-the-audio-button was a moment before. I have no words.

Random musings

If you found yourself in the same room as whatever self-satisfied twit is responsible for foisting "liquid glass" on us and asked the two nearest other iPhone users if they wanted to help administer a swirly, what do you think the odds would be?

I put them over 2/3, personally.

2026-01-31

Baby steps in declarative style

I had occasion, recently, to look at some code one of my younger colleagues had produced. The code works. It's clear. Future maintenance will be straight-forward if a little tedious. It provides a very useful and non-trivial feature. But it is really—extravagantly, even—wordy. The engineer who wrote it knew that: he left a comment to the effect that it was ugly but he didn't know a better way to accomplish the task.

I do know a better way, so this post is intended to put the solution out there for those who need it.

Case study

The feature is simple:

Detect what columns of a CSV file contain specific data by searching the header for pre-defined strings, and subsequently look up that specific data for use while processing the remaining rows.
With which we can ingest the same data from files written by different producers that disagree on what extra columns should be present and what order any of them should be in. At least the headers fields are consistent across all the writers.

To do this we need some storage that will hold the indices (or indicate that columns are not present, so we must support a not-applicable value), and we need some code that walks the list of header fields examining each string and setting the appropriate index (or skipping with a diagnostic if the header is unrecognized). Some of the indexes are required to be set, but we check that after we've set all the ones we find.

A naive approach would be (a) define a named variable for each header index we want to capture, and (b) use a a big if-else if chain (with the terminal else handing the unknown header case) for the "set the appropriate indices" bit.1 That is something like this

//...
    int statusIdx = -1; // -1 for "not found"
    int thingIdx = -1; 
    std::vector<std::string> headerFields; // Setup somehow
    for (size_t i=0; i<headersFields.size(); ++i)
    {
  		headerText == headerFields[i];
        //..
        else if (headerText = "Status")
        {
            statusIdx = i;
        }
        else if (headerText == "Thing")
        {
            thingIdx = i;
        }
        //...
}
As I said above this works, but it's not ideal. Let's start by talking about what is probably the smallest issue: it's long. with that big chain of conditionals living inside the loop over fields (possibly inside two loops if we've built the "iterate over lines" behavior into the same function). And because each index has it's own named variable running "extract-function" on the code would result in an unreasonable argument list for the new routine.2

The bigger problem is that the association of the string names with the related indices only exists in the form of the comparison statements in that big chain of conditionals (that is, far from the variable declarations), so you end up scrolling around to understand what's going on. Good naming can help here, but...

To simplify our life we make the connection between desired header string and the index storage explicit. In the simplest form that looks like

std::map<std::string, int> indices {
    //...
    {"Status", -1},
    {"Thing", -1},
    //...
};
though we could elaborate this in various ways. Having done that, our index setting code becomes a loop3
for (size_t i=0; i<headerFields.size(); ++i)
{
    const auto it = indices.find(headerFields[i]);
    if (it == indices.end()) 
    {
        LOG_INFO("Skipping unknown CSV header field " + headerField[i]);
        continue;
    }
    it->second = i;
}
which is easily extracted to a named function and new supported fields can be added by extending the indices map.

This is much better.

Mind you, it still isn't great and depending on how long the rest of the file processing loop is we may want to elaborate on the system a little. We only use the indices to look up fields in subsequent lines, so rather than writing dataField[indices.at("Thing")] every time, we might want to provide a lookup-field-in-line routine that hides the icky syntax. But that's just gravy at this point.

Elaborations

Extending this is easy, and up to a point is a good idea. Though, you can over-do it. Bring your common sense.

In the case study above the first thing I would note is that our read requires certain columns to be present or the data can't be used. So I want to associate that info with the rest of the data. And my typical approach to that would look something like this:

struct columnData 
{
    int index;
    const bool required;
};

std::map<std::string, columnData> indices = {
    //...
    {"Status", {-1, false}},
    {"Thing", {-1, true}},
    //...
};

int columnIndex(const std::string & key, const std::map<std::string, columnData> &headerData);
bool columnRequired(const std::string & key, const std::map<std::string, columnData> &headerData);
if I wanted to use free functions or if I preferred a class it might be
class HeaderInfo 
{
	struct columnData 
	{
    	int index;
    	const bool required;
	};

	std::map<const std::string, columnData> indicies;

public:
	columnData(std::initializer_list<std::map<std::string, columnData>> l) : indices(l) {}
    
    bool has(const std::string &key) const;
    int index(const std::string &key) const;
    bool required(const std::string &key) const;
};
with the initialization data provided in the read-CSV routine by way of that initializer list constructor.

Critically I don't use two separate data stores that can get out of sync. All the information about our header handling is collected in one place where it can be edited without confusion if needed. In practice many of my data stores in this form go a long time between edits.

Nomenclature

I waffled a bit on the title for this post because I'm uncertain what other people would want to call this approach. There are several obvious candidates out there: "Data-driven design", "data oriented design", and "declarative".

In my mind, the first two are used for the idea of paying attention to access patterns and cache behavior in the way you lay data out in memory. They're a group of optimization techniques, which is not what we're about here.

I went with "declarative" in the title because I think it get much closer to my intent here (the programmer says what they want and counts on the infrastructure to make it happen), but I have reservations in the sense that C++ has very little declarative nature: we have to engineer to the relationship between declaration and behavior each and every time. In this case, that's pretty easy,4 but it can add up to a lot of code if you push the idea hard.

A tooling issue

In this case we want the indices to reset every time we call the "read a csv file" routine, so the data map is scoped to that routine, but I often use some variant of this pattern for static data, with the map placed in global storage. Which valgrind's memcheck tool doesn't appreciate.

You see, various containers in the C++ standard library (definitely including std::map) store some of their data outside the class proper. With any static data store you make this way, those allocations are created on the heap at runtime (though before main is invoked) and they persist until the program ends. But "dynamic allocation still extant at the time the program ends" is one of the things that memcheck detects as a memory leak.

In light of my on-going goal of (not to say obsession with) seeing completely clean reports from compilation, static analysis, leak checkers and so on, this is an annoyance.

You can work around this. Rather than std::map use an array (either c-style or the C++ container) of structured data, and provide your own search abstractions. Rather than std::string use string literals. And so on.5 Or just live with it; by keeping a list of "known okay" reports you can minimize the time wasted on this kind of false positive.


1 In a language with match or a more flexible switch-case construct you could use that instead of the if-else if chain, but C++ is not our friend on that front.

2 The "hard to refactor" bit could be solved by collecting the indices in a single named object struct indices { /*...*/ int statusIndex; int thingIndex; /*...*/}; but we're going to end up doing better than that.

3 Written here in a "modern" but pre-C++20 form because (a) I'm still using C++17 at work so for me maps still don't have contains and (b) because if you did use contains you'd have the search the map twice the in (common!) case where the field is a known.

4 When I tried a full implementation of the elaborated class (just because I wanted to get it right) it came to about 40 lines of code.

5 Or try living in the present: newer standards are supporting constexpr for much more of the standard library, including strings and containers. However that works under the hood, the allocations are not runtime heap entries, so memcheck should be happy.

2026-01-30

The limits of manipulations for "their own good"

It's OK, because Duggee has his gas-lighting badge!

It's an endless question for parents about their children, isn't it? How much pressure and distortion can I, legitimately, use to teach them things,1 to buy a little space, and so on? Some, I suppose, but it must be an ever moving target as the kiddo grows, develops, and just gets better at seeing through our BS. We try to keep in mind that the kiddo must one day stride forth to meet the world with her own skills, opinions, and point of view. We'd like that to go pretty well, so the scaffolding must be dismantled and some kind of model of good-person-in-a-hard-world needs to be offered.

I'll just get right on that.2

But, wait! There's more! My wife and I are smack is the middle of the sandwich, so it's also applies to interactions with our elders. And the answer to that, too, will be an evolving thing. Right now it's just one set, but there is every reason to suspect the others will need support sooner or later. So that's a whole different take on the same kind of questions.


1 In my prior, professional life, it even had a name: "lies to children".

2I wonder who in their right mind would sign off on our being parents in the first place?

2025-12-27

Having your "Smart" and your "Open", too

This is the third of a group of of posts on "Smart" appliance. You don't strictly need the first and second entries one to read this, but they're meant to be building up a common context. In this episode I talk about a mechanism that would relieve most of my worries about these devices while allowing manufactures to maintain control of their trade secretes and the presentation of their interfaces.


My complaints in the first post aren't intrinsic. Instead they are complaints about a particular implementation of the smart model where the physical device requires a specific (rather than generic) piece of paired software; in that implementation our intellectual property regime around software puts the manufacturer in control your ability to have that software, and consequently puts the manufacture in control of your ability to use an physical device that you bought. That is totally unacceptable.

Out strategic goals are

  • Possession of the physical device grants access to its functionality1 because the requisite software is generic enough to be re-implemented on any suitable platform.
  • The manufacture gets to keep their trade secrets to themselves.2

That's an interesting pair, because the first requires that the trade secrets be physically embodied in the machine (otherwise you can't guarantee that the functionality moves with the physical object). But that seems to be in tension with the second requirement, because how can the manufacturer control trade secrets if they're trading freely around the economy?

The thing is that the control software (which is already on board) also encodes the trade secrets. Anyone with the right tools and knowledge can already can extract the firmware, decompile it, and sus out the meaning of resulting code. It's just that that's a pretty hard trick (those boards don't need debug ports and I'd be unsurprised to find that many don't have them) and consequently time consuming and expensive. Then, as always, the legal regime puts up further barriers to someone trying to compete using that approach.

And we deal with systems that have those properties all the time. I'm describing a client-server architecture. The appliance is the server, your phone or tablet is the client, and the manufacturer can hide as much detail as they want server-side. By using open protocols for interchange and open standards to present the interface consumers get a extortion-free way to talk to the appliance, and in return manufacturers get reduced software development costs and all-platform3 functionality for free. It's that easy.

My hot-take, off-the-cuff, proposal for the whole thing:
HTML5.

Literally put a little web-server inside every appliance. They already have non-trivial computers and at least one of WiFI or Bluetooth, so this is not a stretch. In the worst case users can use a plain web-browser to access it.4 With HTML5, manufacturers can control every aspect of their interface (I mean their pages can just be a canvas if they're that hung up on controlling the appearance), control how much functionality is pushed to the client-side, and so on.

What's not to like?5


1 All of it. If you own the thing, you own it. This is non-negotiable.

2 Within reason. There is no way to guarantee this absolutely, and (importantly) never has been. Anyone with the tools and expertise has always been able to reverse engineer a product, and it was only ever the high cost of reverse engineering and IP law that prevented them from taking that route to develop a competing product. We're looking for a regime where the level of difficulty in obtaining and profiting from the trade secrets remains similarly high.

3 And not just iOS and Android, either. Everything, everywhere, all at once. As it were.

4 I imagine the ecosystem will rapidly spawn a genre of specialized appliance control apps with a HTML renderer at the core, and featuring convenience functions for organization and access, but a plain web-browser provides a fallback position.

5 Okay, so there is the addressing problem for devices that use WiFI. Some kind of discovery mechanism will be needed, and I don't know— off the top of my head—what the options for that are. But it's not like this is a new problem: printers and scanners, especially, already handle this in a variety of ways. Similarly bluetooth has a pairing problem to solve, but that's an issue for existing bluetooth devices as well.

2025-12-20

Why "Smart" appliances might make sense

This is the second of three related articles. If you haven't seen it already, perhaps you should read the first installment.


Some devices have very simple interfaces: they're either running or not, so they need a switch. But then, maybe there is a thing you make stronger a weaker, so you add a dial or a slider. And maybe it can run a couple of ways, so you add a selector dial. Maybe the user needs some progress feedback, which means some kind of clock.1 And so on.

In my youth, a typical washing machine, dryer, or dish washer had a handful of controls supporting as many as a dozen modes of operation, and that felt like progress compared to the kit my parents grew up with. Yeah! Living in the future!

This is the control panel from the dryer at Casa NoSwampCoolers. There is a power button and a separate start button (because power means the controls are active and start means the machine is running), a mode-select dial with fourteen options, four categories of adjustable parameters (some of which only apply to some modes), six additional Boolean settings (again, applicability varies), and a time-display-and-control group. I'm not sure if that reaches a thousand front-panel accessible combinations but it is certainly hundreds. And there are extended features only available with an app.2

Okay, so we've established that (at least some) modern appliances require complex interfaces. But we've also established that you can build the interface into the device. So, how does that justify connecting it to your carry-around-computer-thingy?

Well, we're in engineering trade-offs land. We get to compare costs, convenience, maintainability, utility, and user preferences between alternatives. And how we rate some of those depend on how the machine works. If you have to physically load and unload a device for it to be useful (as in clothes washers, clothes dryers, and dish washers) then remote start is less helpful compared to remote access to your car's defroster.

Settable defaults are nice
In principle, to know that my dryer is doing what I want I have to memorize the desired state of approximately a dozen controls, and check each of them on the control panel. In practice I memorize the smaller number of things it takes to get from "just booted" to the state I want: switch the mode to "Speed dry", set the temperature to low, increase the time, then go. Wouldn't it be nice if I could set the default state or name several default states? If there is a win here it's for "smart"
Control panels cost money and represent additional points of failure
Controls are a pain. They have to be robust, when the options are discrete they should be unambiguously in one state or another, they should give clear feedback, and in the physical realm all of that takes engineering. The cheapest switches and dials general lack something. I'm not in this business, but over the years I've talked to people who are. Controls often represents a surprising portion of the cost. To be sure, digital interface can fail too. If the magic smoke gets out of the computer on the device, your done with either kind of controls.3 If your phone smokes, you need a different interface device. At least partly in favor of "smart"
You're right there anyway
As mentioned above, some devices require your presence. For those I really want a on-device control set for the primary features. Because otherwise they require the presence of you and your phone or tablet. But I'm pretty happy with our machine that presents the big easy stuff on the panel and leaves the fiddly choices for a computerized interface. Definitely favors some physical controls on machines you have to attend in person
Translation
Did you notice that the controls on my washer are labeled in English? That's built into the physical medium, so a French-speaking, would-be user can't just change it. Now the company could (and presumably does) provide that part in multiple language variants, but that becomes a logistical headache and thus a cost and you can't easily switch back and forth. Adding translation to a software interface is not free, but (with some support from your OS or framework) it can be relatively painless, and users can change the language on demand. That's cool. A reason for pixelated UI, where ever the display is mounted
Maintainability
Once a physical control panel is engineered, manufactured, and sent into the wild changes are hard and expensive. In principle, software changes are easier, though if the software in question resides on the machine, you'll need to provide a firmware-update facility of some kind.4 If there is a win here it's for "smart"
"Just give it a proper display" is a good idea, but you already have a display...
There is nothing magical about using your phone or your tablet that enables digital UI. You could put a display (probably a touch display) on the device and then have a programmed interface. Yeah!. But ... how many pixels are you willing to pay for? And on every appliance in your house? what about physically small devices like my espresso machine? And those question lead to the idea of sharing a single interface device. Your potential customers almost all have a phone or tablet already,5 why not just borrow that display? Favors "smart"
Human stuff
This is hard for me. I'm not trained in UI/UX. I haven't done any surveys. Basically this whole essay has been me ranting about how I feel. But people sometimes care about appearance, and sometime don't like to learn new things. And so on. I'm not going to guess how this breaks for smart versus not smart.

Anyway, the next article presents what I think might be a viable approach that everyone can live with.


1 In some instances the dial that the user used to set the run-time was a mechanical clock and would wind back down also serving as the time-remaining indicator. Parsimonious design, that.

2 This is the sort of situation that prompted the third Smart Appliance rule. That dryer is very useful even without the app. Indeed, we've never used the app. Mrs. NoSwampCoolers tells me she has used the app for the matching washer (with a very similar control panel) once in the fourish years we've owned these things. Once.

3 Yeah. I'm just assuming there is a computer in there. My sense of it is that no one is designing these machines with end-to-end analog circuits anymore. I once did a few hours of analog diagnostics on a Kenmore model 40, but I'd expect to find a MCU behind any reasonably modern control panel.

4 My car's entertainment center has a SD card slot hidden behind a pop-up panel.

5 If they don't we're talking an additional up-front cost for them, but if they can afford the appliance, ... well Walmart's website lists tablets starting from under US$50 as I'm writing this..

2025-12-19

Little surprises #10: iterators, half-open intervals, and git range selections

Let's talk about for loops. That's not actually the subject of the post, but we have to start there, hit a couple of stops along the way, and only then complain about git.1

Back in the misty depths of time, there were programming languages that indexed their arrays starting with one. If you write that in a c-like syntax you end up with something like

int array[ARRAY_SIZE];
// fill it somehow
for (size_t i=1; i<= ARRAY_SIZE; i++)
	print(array[i]);
where the end condition is controlled by <=. That has never really gone away—despite the efforts of some influential people—but I want to direct your attention briefly to the same for-loop in a language that uses zero-indexed arrays. Now idomatic code uses a strict less-than comparison:
int array[ARRAY_SIZE];
// fill it somehow
for (size_t i=0; i<ARRAY_SIZE; i++)
	print(array[i]);
but you could also use a not-euqal-to (!=) because the upper limit of the loop no longer represents a cell of the array that is accessed. Instead it represents where a cell would be if it were one-past-the-current-end.

Pointer and Iterator loops

Of course, you don't have to use an index for a loop counter. K&R is full of examples where they use a pointer for processing a (null terminated) string:

const char *s = "Yellow whirled!";
for (char *p=s; *p != '\0'; p++)
	print(*p);
or a linked list:
LIST_NODE *list;
// fill it somehow
for (LIST_NODE *p=list; p != NULL; p=p->next)
	print(p->payload);
In those cases the termination condition is a not-equal comparison to something that is (again) not processed.

And that is the pattern than many languages (especially the ones I use regularly) use for iterating a (possible subset of a) collection. The beginning iterator designates something that is to be processed and the ending iterator either signals the lack of further data or designates data to not process. In C++ this is the pattern for the algorithms library (where you hand the routines explicit iterator pairs), and by ranged-for loops where the syntactic sugar calls std::begin and std::end on the object to be iterated. The end-marker does not represent data to be processed.

On to git

Some git operation let you interact with groups of prior commits. To inspect a sub-set of the history, run a partially automated bisection process for finding where an issue was introduced, or (as I was doing when I discovered2 this behavior) to select a set of commits to "cherry-pick" to another branch. Unsurprisingly the command line syntax needs you to say what commits you want, and listing more than a few hashes explicitly is a pain. Luckily there is a "range" notation: [Early commit reference]..[later commit reference].

Now, if you use this notation with cherry-pick it will apply your selected commits to the new branch in time order: starting with the earliest selected commit and working it's way steadily toward the latest. Which is exactly what you would expect.

What you probably wouldn't expect (unless you, ya'know, actually read the documentation), is that it will exclude the earliest commit you entered and included the latest. This was so surprising to me that I actually did it wrong a second time before I started scouring the manual for an explanation.

That is a half-open range like we find in the discussion of iterator for-loop above. But instead of being [start, end) it is (start, end]. What. The. Absolute. Heck?!?

That said...

Interestingly, if you look a little deeper, there is a reason. Or at least a way in which this behavior arguably conforms to my description above.

Start by understanding how a git repository is structured. Each commit contains a record (effectively a pointer-to) all the commits that act as its parent(s) (there might be zero, one, two...). But commits are immutable once created, so they can't be amended with pointers to their descendants. The effect is that (assuming your range selection represents a branchless part of the graph3) you're looking at a linked list with the last commit as the head and the earliest commit at the end (of the range you care about, anyway).

So, even though commits are applied in time-order, they are found starting from the last event and working back in time. The docs make this clear: the selection is all the nodes reachable from the final commit, but not reachable from the first commit.4

And in that view, this is exactly like the linked list example I wrote above.


1 A little like eating your vegetables before dessert.

2 The mere fact that it is clearly documented if you bother to read doesn't affect my right to use that word. Does it?

3 I haven't actually tried it yet, but I imagine that selecting a span with this syntax that has a merge in it would include a large historical sub-tree you don't want.

4 And a commit is reachable from itself, naturally.

2025-12-14

The "Smart" appliance policy at Casa NoSwampCoolers

By way of introduction to some follow up posts: appliances that require a external computer to make them go are a kind of cyber vulnerability. The utility of the device is dependent on the availability of a working interface and thus on the whims of a third party. If they drop support; if they decide to lock some features behind a paywall;1 if they don't support the app on the platform you use, your device is worthless.

While someone could, in principle, program a new interface there are a number of problems:

  • Access to information about the device side of the interface. This stuff is trade secrets, and the holders rarely see it as profitable to share it, even if they are no longer supporting the affected devices.
  • Good engineering practce may suggest using a a common code base, but manufacturers are also willing to switch hardaware platfomrs to minimize costs. The result is a lot of diversity even within single product lines, as witnessed by the long running struggle to support various peripheries in the linux kernel.
  • Finally there are legal barriers to the "just hack it scheme". In the US, for instance, the DMCA, means that the most trival effort by the manufacture to "protect" their kit makes the hacking job a felenoy. Until the right to repair is legally recognized,2 this multiplies the difficulties improsed by the others.

So here in Casa NoSwampCoolers we have a three part policy:

  • No device which requies an account registered with the manufacturer.
  • No device which requies an app provided by the manufacture to access it's core function.
  • Any device which requires an app to access special features or functions must be explicitly discussed by the grownups prior to purchase.

The second part of this series will address the engineering reasons in favor of "use you device to control it", and the third will look at a solution that lets us have the upsides without the downsides.


1 No names, BMW.

2 I consider that a cause worth my political dollars. Would you care to join me?