Showing posts with label Surprise. Show all posts
Showing posts with label Surprise. Show all posts

2026-07-25

How many featured guests does it take to change the "band" of an album?

Let's say, you're writing a music app. Something like Apple Music, Amazon Music, Spotify, or whatever. You will, of course, want to offer your users multiple ways to find music. A flexible search interface, a list of songs, a list of albums and so on. The fun comes if you decide to offer a list of artists. When should an album show up under a particular artist? How do you handle ensembles that aren't publishing as a named entity (i.e. it's by a bunch of people and not "by a band")? Worse still, how do you deal with tracks that include a featured guest artists on an album that is otherwise by a single person or band?

As a programmer who cares about users, my vote would be to accept some duplicate presentations due to fuzzy matching rather than risk disappointing a user who disagrees with my way of answering those questions. But that doesn't seem to be what Apple picked. Indeed, I simply can't deduce the logic (if any?) underlying how Apple Music handles these questions. In the album list one album can show up more than once, partitioned according to the performance credit for each song. An album by a band that has one song with a featured guest performer isn't enough to make that band show up in the list of artists.1 WTF, Apple? What are you thinking? Just ... WHY?!?


1 I've ripped the True Love Never Dies from CD, but Otherwise doesn't show up in my artists list, because I only have that one album at it has one track featuring Ash Costello. Grrr...

2026-07-01

Paging Generative Freud. Dr Freud, you are needed in the psych ward

I had a brainstorm today about a possible prompt to test the edges of LLM conpetence. It's longer prompt than most of the ones I use and demands a longer answer, so context lengths have to be expanded and it runs slowly on available hardware. As a result I've only begun investigating it.

But it triggered an idea for another "off the wall humor" type question which is quite succinct. The thing is that when I started asking models about that one I got a surprise, and thinking it might be perculiar to a particular model family I tried some others; and the surprise persisted. What. The. Absolute. Heck?

There is an old saw that a boat is a hole in the water that you throw money into, which is not the core of my prompt but has a similar flavor. What I'm actually asking the models to react to is a pair of definitions of two classes of boats in terms of how they make you broke. If you've spent anytime in boat-owning circles you've probably heard it. The joke is funny because (a) it acknowleges that boats are conspicuously expecnsive to own, and (b) it distinguishes between motor boats and sail bots, and you can order the definitions so your audience feels good about not being in one category until you hit them with the definition for the class of boat they own. Then it's rueful smiles all 'round.

Only ... model after model has wanted to interpret it as some kind of financial metephor. The difference between spending big for lots of velocity and binding your time doing things right in the hopes of achieving a better outcome. Or something like that.

Where are they getting that? Does their training data inclue lots of articles from the popular business press? Or something?

Weird.

Oddly it seems to come outstronger when models are allowed to reason.

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-03-02

Yeah, why doesn't she?

One of my habits1 is singing along to whatver music is playing in the car. Today I was belting out The Warning's2 When I'm Alone while driving my daughter to gymnastics. She's usually heads-down over her tablet in the car, so I have to make my own entertainment.

As the song wrapped up she asked me what the singer meant by the "hole in [her] soul".

These are the kinds of moments that make parenting a minefield, but also so worth it.

The song is about maintaining outward calm in the face of inner turmoil in a inescapable situation,3 but I don't think4 my seven year old really has a reference for that. So I tried to explain that in this case "soul" means how you're really feeling inside and told her somthing like "The whole song is about making the people around you think you're OK, while you're really, really sad on the inside". That seemed to satisfy her at first.

Then, after a few moments of quiet, she asked me "Why doesn't she just tell them?"

Stonkered for the second time in as many minutes. Alas, I suppose that she will understand some day.


1 Good? Bad? Fine when I'm alone but annoying otherwise?

2 I'll try to keep the pathetic fan-boying to a minimum here, but if have any interest in rock and don't know this band it's worth a little of your time to check them out. In addition to the song noted above consider Choke, Evolve, Disciple, or Hell you call a Dream as introducers.

3 Written by a homesick fifteen year-old and a better expression of that state of being than I've managed in more than fifty years of being at least a little messed up. Go figure.

4 Hard to know with absolute certainty: once you send them to school you don't know everything about what's going on in their life unless someone tells you. I feel pretty confident that either the child or a teacher would have clued us in but there is still room, however small, for doubt.

2025-01-25

Little Surprises #9: too helpful edition

In Qt5, this #include <QString> #include <algorithm> #include <vector> int main() { std::vector<int> v{0, 1, 1, 2, 3, 5, 8}; QString qs("target"); // Search the vector for the string std::find(v.begin(), v.end(), qs); } will build (compile and link). There are very few circumstances where you mean what it will actually do (in this case it's a very expensive no-op), but it is valid code.

This works because of a chain of "helpful" features:

  • You can construct a QChar from an int: QChar(int rc) noexcept
  • .
  • There is a equality test between QChar and QString: bool operator==(QChar lhs, const QString &rhs) noexcept
  • .
  • The find template uses operator== between the contents of the vector and the target.

Each of the behaviors is desireable in isolation, so we don't want to remove any of these options entirely. What causes the surprise is the implicit nature of the type conversion.1 Core guidelines C46 and C164 discuss the uses of explicit annotation to prevent this kind of thing.

BTW: the above code will not compile in QT6 which is a small but significant improvement.


1 Shout out to all the strong typing fans out there.

2024-09-10

ODR? Fuggedaboutit!

I was hacking together a thin patch at work today. Tyring to get the logging calls from a library to play nice with the rather hacky logging infrastructure of a legacy project we maintain. Basic translation worked fine, build support took a couple of tries but seemed to be coming together, but then there was the matter of Level enum and getting string representations for the values ... so I shoved a copy of the original enum and a definition of levelText(Level level) into the header.

Yeah, the definition. In the header. I should've know better being, as I allege I am, a big-boy programmer. Well, nature took it's course and the compiler complained: I was violating the One-Definition Rule (oh, holy of holies!) on a massive scale.

But I was ready to go to lunch and I didn't want to deal with hard problems like where in the project tree to put the source file that ought to hold the implementation and how to add it to the build system and so on. It was at this point that some rebel-without-a-cause, burn-it-all-down inner-self threw up a suggestion: why not try something like:

template <int=0>
std::string levelText(Level level) 
{ 
    /*...*/ 
}

I mean, what could it hurt?

Rhetorical question, of course, but let's list some of the failing of this cutesy little trick (which compiles):

Hacky, non-idiomatic, confusing
Any C++ prgrammer who sees a template declaration will expect you to do something with the template parameter, which we don't. Time will be spent look for the use and trying to suss out what the paramter might mean. Worse, if they're experienced they'll notice that it's a non-type parameter and be expecting some kind of clever (read tricky) metaprogramming, which there isn't.
Contibutes to slow build times
Evey template read takes a little more time and little more working set. So does every template instantiated. I menetioned that this is a legacy project, right? It already takes 12-15 minutes to build on a fast hexacore machine with lots of memory.
Contributes to bloated binaries
Every template instantiation creates a separate bit of code. We're writing that function for every source file that includes the header.

Once I'm fueled back up I can go back to the office and do it right. But now I know a new stupid C++ trick. Yeah!

2024-07-14

Color me impressed with up-to-date LLMs

I only recently started playing around with LLMs, and I started with somewhat dated models.1 Yesterday I signed up for ChatGPT and I can see why some former sceptics have gone fully bought in. The 4o model is impressive.

My main investigative tool was to pick things that I know something about, and ask the model a question from those fields neither trivial nor really hard but requiring some nuance. I tried to select ideas with a range of popularity because I believe that the volume of writing on a subject may influce the "skill" the model exhibits. My subjects so far:

  1. Discuss the Epistemological implications of Godel's incompleteness theorem.
  2. Summarize Freemon Dyson's "Time Without End" paper. (If the model did well on that I followed up with a question on which of the papers conclusions had been overcome by more recent developments in cosmology.)
  3. Summarize Lamb's "Anti Photon" opinion piece.
  4. Discuss the similarities and differences between the movies Ghost Dog and Leon: The Professional.
  5. Explain the continued strong culture of short form composition in speculative genre fiction as compared to general fiction
  6. Differences between classical and modern guitar
  7. Prepare a packing list for a lightweight wilderness survival kit to carry on my day hikes in the desert southwest.

My highly unscientific observation suggest that items 1, 6, and 7 are the more popular topics and should provide mode source material for the models. The movies question (4) is weird: each movie has been extensively discussed so there is a lot of source material for each movie, but I'm not sure how much human generated comparison text is out there. Then I would rank 2 and 5 a more common than 3.

I didn't get any major factual errors from ChatGPT4o. I's main fault was that much of the writing was bland and characterless on all responses. Of course, the desireablity of "character" is context dependent: an encyclopedia or other raw factual source should be pretty neutral and that's what most of the responses sound like. ALso I didn't do any prompt engineering to elicit character: I just gave the model the question and let it go.

I do have two speific comments on ChatGPT's responses. First, it's interpretation of Lamb's paper if different enough from mine that I wasn't particularly happy with it, but I wouldn't be surprised to learn that other trained and capable physicists subscribed to that interpretation. Second, the answers it gave for the movies really sounded like they were drawn almost entirely from reviews for each film in isolation:2 Ghost dog this; Leon that. Over and over again.


1 My initial goal was to investigate the possiblity of using a LLM as a purely local coding assistant (that effort continues). At work our security guidelines imply that no internal code or design details should be release to the wider world or sent over an unsecured internet connection. Obviously just using CoPilot from VS Code is out. But I started this investigaction on a personal machine to find out if the tooling would meet requirements before going in search of management buy-in. Alas, my "best" machine is totaly unsuited for even late 3rd gen models. I can run models up to about 1 billion parameters smoothly and easily. A 7B parameter model (zephyr) is too slow for use in a work-flow (seconds-per-word), though it's fast enough to learn about the models. I have put a 22B parameter model on the machine, but it runs at minutes-per-word.

2 Indeed that observation is what prompted my comments above.

2024-03-21

Algorithms header knowledge check

Note some late additions marked with *.

* After posting this I began to feel it needed a little bit more detail. And then that it needed quite a bit more detail.


The C++ standard library's algorithm header has a routine sutiable for counting the number of places of disagreement between two equal sized collections of elements. What's it called? Hint: it's not called "count_differences". *Nor is it called count_if (unless you happen to be using C++-23 because count_if doesn't have an overload for parallel containers.0) My work projects are in C++-17 and my home projects in'17 or '20.

Answer
inner_product

*One approach is to write a "sum" function that adds just as in the usual inner product, but the make the one that would do element multiplication in the standard inner produt return zero when the two values are equal and one if they differ.

For that matter, what educational backgrounds would prepare you to recognize that as the routine you want?1 How does this compare to Kate Gregory's story about partial_sort_copy and how it would be better called top_n?


*0 C++23 doesn't introduce a parallel overload either, but it does introduce zip_view and zip which will allow you to efficiently produce on single container of apparent pairs from the parallel containers. Then you can use the single-container version of count_if. Obvious. Right?

1 My combination of physics and prior experience with the algorithm header's love affair with having a user-supplied-predicate-to-change-the-behavior overload meant that I spotted it as soon as I read the name, but ... that's a rather esoteric requirement for user's to know what they're seeing.

2023-08-29

Why isn't there a slider version of QInputDialog?

I need a quick-n-dirty UI element in my QT-based application for selecting a number. A number with a well defined lower limit (certainly not less than one, but honestly even 5 is kinda silly) and a fuzzier but still very finite upper limit (somewhere between 60 and 100, I'd say).

Now, there is QInputDialog::getInt which is what I'm going to end up using, but ... it's a natural use-case for a slider and there seems to be no similar convenience class supplying that particular functionality. Grrrrrrr!

2023-04-20

Yep. That was kinda dumb of me.

Consider the canonical Meyer's Singleton implemetaion in C++ (2011 or later standard): class Singleton { Singleton(); ~Singleton(); public: Singlton(const Singleton &) = delete; Singleton & operator=(const Singleton &) = delete; Singleton & instance() { static Singleton s; return s; } }; It has all the properties you might want of a Singleton:1 things like guaranteed single initialization and thread safety. Plus, it's easier to remember than any of the other functional variants I've seen.

But there is a subtle trap in the fact that I haven't shown you my implementation of the c'tor.2 One that I just tripped on in a big way. What happens if you were to call instance() in the constructor?

Now, it is unlikely that anyone will write Singleton::Singelton() { instance(); } and overlook the silliness of it for long after the program hangs during testing. However, writing Singleton::Singelton() { supportRoutine() } where supportRoutine() calls helperWidget() which calls instance() is a lot easier to miss for a few frustrating hours. Trust me.

This is about the object lifetime model in modern C++. And honsestly I didn't know enough about the hoary details to figure out what the standard requires when I realized what I had done, but MSVC 2019 appears to enter an infinite recursion. Luckily the internet is full of people who have more (or at least different) expertise than me, so I was able to get some help. The first source that my google-fu found was a blog post by mbedded.ninja which includes an explicit call-out to the standard3 telling us this is undefined behavior.

So, yeah. Don't do that.

Late addition: on reflection, the reason I got into that pickle in the first place was that I was using the Singleton both to manage an OS resource and and as a repository for utility code related to the resources. Having them in the same context made it too tempting to trigger some of the utility code from the c'tor and involke the recursion. Re-writing the code to better folloow the Single Responisibility Principle made the loop imnpossible in the first place and made me re-think when to invoke the utility code.


1 Assuming, of course, that you want a Singleton; more on that in another post.

2 I've left the d'tor unspecified as well for a philosophical reason: you should always decide on their implementaion as a pair. I don't want to commit to = default; for the d'tor until I know what I'm doing in the c'tor. In the instance that brought the subject up I was grabbing OS resources in the c'tor and so needed a non-trivial d'tor that would nicely give them back. Cue "but technically's" about the destruction order fiasco. I said I would discuss the (often ill-)advisability of Singletons in another post.

3 Section 6.7 paragraph 4 which says in part:

If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.

2023-01-26

Unanticipated milestone

It's like a coming of age ritual, except not as fun.

There will come a time when your doctors describes your situation in reference to "a [wo]man of your age".

2023-01-07

Uhm...

I got a Kindle Scribe for Christmas. Yeah, now I hav an e-reader I can actually read papers on! Cool.

Of course, this just means I've been sucked into Amazon's ecosystem. You can only use a few formats on it, but that includes PDF. Except...

  • Annotation support on most PDFs is limited.
  • To get full annotation support you have to submit the file through "Send to Kindle"1 where I suppose it is pre-processed in some way and them made available for downloading to your machine.

Fine. I'll pretend I don't think they're spying on my content, and I'll even try to believe that they are spying responsibly. But honestly they already know a lot about me.

But that doesn't mean that there aren't any surprises here. You see, the machine has a web browser. It's not very good, but it's there. That browser won't save PDFs directly to the machine, which is a little weird, but the big oddity is that it also has no facility to route them through Send to Kindle. To somehow manage to make matters worse, the "Send to Kindle" page has a link for a "Send to Kindle" browser extension!

Seems like a no-brainer to me. Perhaps I should send them a note, but they make that weird, too.2


1 More "Send to Kindle" breakage: it will convert ebook files to a Kindle friendly format for you, but will silently drop on the floor any files already in Kindle preferred format (that perhaps, you downloaded from a DRM-free source like Standard EBooks).

2 The device supports a "Contact Us" feature, which lets you send missives into the void, but they specifically disavow any intention to hold a conversation as a result (or even just telling you what they are doing about it). There doesn't seem to be a customer facing issue tracker where you can see that your suggestion is already in progress or follow deliberations on how to handle an issue. I'm spoiled by the culture of the open-source world.

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.

2021-09-03

Observation of the day

We all come into this world with our Pleistocene brains, ready to take on the information age.

2021-02-13

Little surprises #7: more Qt versus the preprocessor misery

Trying to be a good kid. Writing tests as I code. Testing the edge cases. "Hey, this should throw, does Qt have a test for that?" Yeah, its QVERIFY_EXCEPTION_THROWN. Great, let's use that!

void suspisiousFunctionCallThrows()
{
    // Define badInput and otherParam;
    
    QVERIFY_EXCEPTION_THROWN(suspicisouFunction(badInput,otherParam), std::runtime_error);
}

It doesn't compile. Why not? Commas again, of course.

And it is conceptually easy to fix: you just create some blind wrapper than make the offending call without taking multiple argument.

void suspiciousFunctionCallThrows()
{
    // Define badInput and otherParam;
    
    std::function f = [&]{
    	suspiciousFunction(badInput,otherParam);
    }

    QVERIFY_EXCEPTION_THROWN(f(), std::runtime_error);
}

Not exactly a featured stop on the "Look at our transparent tests" tour, is it?

2021-01-05

As if you needed another reason to hate the preprocessor

C++ inherited from c a compilation model that discards nearly all information about types and symbols. This is a mixed blessing. On one hand it simplifies a number of things and made c portable to a wide varienty of machines including some with minimal resources. On the other hand it means debuggers need added support to do their work and that reflection is not natively supported (in c) or requires special work (as in RTTI in c++). Unfortunately there are some very nice things you can do in the testing domain if you have reflection that are quite difficult without it.

This can be worked around in a number of ways, but most approaches make heavy use of the preprocessor: using function-like macros as well as the built in location macros (__FILE__, __LINE__ and their more modern counterparts). Indeed, your humble author once wrote a unit-testing framework for c and c++ in a combination of c-preprocessor and GNU make.1 More seriously Qt's native signals/slots mechanism and test framework make use of a variety of macros (Q_DECALRE_METATYPE, QCOMPARE, and so on).

Enter c++ templates. In particular templates taking more than one parameter. For example, say I'm using QTest and I want to perform a test of a computation that returns a three element stadard array of floats. I write something like QCOMPARE(actualArray, expectedArray);, compile (no problems) and run the tests which results in a hard crash in the bowels of qmetatype.h.

Oh yeah, I needed to declare the type to the metatype system.2

No problem. Scroll to the top of the file and add Q_DECLARE_METATYPE(std::array<float, 3>);, but that won't even compile (or if you're using Qt Creator the linter will catch it for you). Do you see why?

Q_DECALRE_METATTYPE is a macro. It's arguments are parsed as comma separated plain text. Which means the preproecssor reads that line as having two arguments where only one is expected. Crap.

To be sure you can work around this issue. My favorite approach is to use a type aliases like

using floatAry3 = std::array<float, 3>;
Q_DECLARE_METATYPE(floatAry3);

Keep in mind however, that this approach has its own trap: Qt's metatype system won't let you register the same type twice. The same underlying type, not the same name. Which means that you have to use a single name for each type to avoid inadvertent duplication, but you can't use the underlying typename for any multiple argument template types.

Anyway, the point is that you can't mix funciton-like macros with multi-argument templates without pain.


1 About all that can be said for it is I learned a lot and it worked well enough for me to use on some of my toy projects.

2 Not that you can tell from the crash or even from the stack trace in the debugger. All the behind the scenes magic that goes into QTest hides the origin of the error. I assume that most every QTest user has spent a frustring hour or so learning this the hard way.

2020-10-19

Why is it always "percentage"?

I got a side assignment at work Friday: write a test harness and tests for a pieces of software a colleague is writing.

This is suppose to be a reference implementation, so having different people write the code and the tests is a good idea: it will tend to expose ambiguities in the specification and unstated assumptions in the implementations.

"No problem," I said, "but I'll need a copy of the spec."

As I'm reading the spec today I come to:

The input value, given as a precentage in decimal format (a value between zero and one)

Sigh.

Now, there is a strict reading in which this is technically correct, but that reading conflicts with the lay uage of "percentage" in a way that leaves room for ambiguity. The parenthetical clause is necessary to resolve the ambiguity. In the tradition of academic physics we'd have written "The input value, given as a fraction" explicitly because there is no chance that it will be misinterpreted.

2020-07-26

Aerial refueling?

There is a dessert willow growing in a neighbor's yard that overhangs the wall between us. It's pretty and provides us with some nice daytime shade so I don't grumble about the bits that drop off and land on the artificial turff the last ownerr installed.

Today my wife and I were admiring the new bird feeder when a blossom fell from the tree. A hummingbird perched on the feeder tried to drink from it as fluttered downward. No idea how succesfful the attempt was, but the maneuverability of those birds is breathtaking.

2020-06-24

Little surprises #6: "Stop helping me!" edition

I got a little technological help today. Unfortunately it was not the helpful kind of help.

I attend some remote precense meeting that are mediated by a bespoke telephone-based system rather than by internet. This is driven in part by the institutional inertia that comes with having had the system for a long time, but mostly by security concerns. So I dial in, switch to speaker, and prop my cell phone against my monitor stand. It has to be a cell phone because we don't have a landline.

The conference involves our sponsors, so we keep a separate chat channel open for communication amungst ourselves.

On the conference today I finsihed answering a questions and waited for a response. And waited. And waited. And noticed that the line seemed deader than usual. I enquire on the chat channel if there is a problem with the conference, and a colleague (another parent of young children as it happens) writes back "I don't know, but someone has to get in the car seat to get a ice cream."

A horrified glance out the window is enough to confirm the worst. There is the babysitter loading the toddler into my wife's car. The phone has linked to the car and my colleagues and sponsers are hearing all about the proposed trip to get ice cream.

I want it to do that, but not now

I have no one to blame but myself. Unless I can blame Apple a little, too. That sounds good. I'll do that.

Usually I want the phone to pair with the car as soon as it comes on and without asking me if that is a good idea. But maybe the behavior should be varied when there is already a phone call in progress? Especially if I am already using the speaker. Or something. I'm frustrated that progress in AI has produced a lot of powerful spying tools and general creepiness, but damn all context sensitive decision making for real life.

2020-06-21

Little surprises #5: qmake edition

The last edition of "Little surprises" reminded my of an incident from a while ago.

This one involves the input file format fo Qt's qmake tool.

Like a lot of other tools, the input language uses # to indicate a comment extending to the end of the line, and \ as the last charqacter on a line to indicate line continuation (the current logical line is extended to include the next file line). It also uses lists of white-space separated tokens as in:

SUBDIRS=external-library module1 module2 tests

If a list gets to have more than a few entries, it can be made much more readable by using line continuation:

SUBDIRS = external-library \
          module1 \
          module2 \
          tests   # Should anything go here?

A practice further encourage if you use Qt creator because the IDE builds lists of files and directories in this way.

What about the last line?

Should you put a line continuation marker on the last line of such a list?1

Pros:
  • consistency (which may appeal to any obsessive tendency that you might have)
  • reduces the number of ways to make a mistake if you reoder the list
Cons
  • you don't need to
  • you need to worry about what is on the next line, introducing a new way to make mistakes

As I can be a little obsessive about consistency in code and insist on a whitespace line after a list like that anyway I choose to put the trailing continuation in there. The thing that I discovered is that in qmake the "need to worry about what goes on the next line" bit includes comments. That is, if I write

SUBDIRS = external-library \
          module1 \
          module2 \
          tests \
# Explanation of what I want to do next
ANOTHER_KEYWORD

The system will consider ANOTHER_KEYWORD as an item on the list.

I'm not aware that this is documented anywhere.

What?

From my point of view this the violates the principle of least surprise. If I append the comment to the line containing test, then I have a end-of-line comment and that should be the end of it. That said, if I think about actually parsing a file like this I can see two implementation strategies that would lead to this behavior as a side-effect,2 and though they are not the ones I would have chosen I don't think they are dumb either.

Arguably this lends weight to the "don't do that" side of the argument about continuing the last line, but I have chosen instead keep the consistency and insist that such a list must be trailed by a empty line.


1 This kind of question doesn't come up in my mind alone. Note that C++ enum offers explicit support for ending the list with a comma.

2 (A) Multiple passes with comment elimination coming before line continuation and full line comments removed entirely. (B) Line-at-a-time parsing and full line comments are handled by silently loading the next line.