In the light of the so-called “Twitter migration”, I’ve spent a lot of the last week helping people new to Mastodon/the Fediverse in general to understand it. Or at least, to understand
how it’s different from Twitter.1
If you’re among those jumping ship, by the way, can I recommend that you do two things:
Don’t stop after reading an article about what Mastodon is and how it works (start here!); please also read about the established
etiquette, and
Don’t come in with the expectation that it’s “like Twitter but…”, because the ways it’s not like Twitter are more-important (and nobody wants it to be more like Twitter).
The tools, protocols and culture of the fediverse were built by trans and queer feminists. Those people had already started to feel sidelined from their own project when people like
me started turning up a few year ago. This isn’t the first time fediverse users have had to deal with a significant state change and feeling of loss. Nevertheless, the basic
principles have mostly held up to now: the culture and technical systems were deliberately designed on principles of consent, agency, and community safety.
…
If the people who built the fediverse generally sought to protect users, corporate platforms like Twitter seek to control their users… [Academics and advertisers] can claim that
legally Twitter has the right to do whatever it wants with this data, and ethically users gave permission for this data to be used in any way when they ticked “I
agree” to the Terms of Service.
…
This attitude has moved with the new influx. Loudly proclaiming that content warnings are censorship, that functionality that has been deliberately unimplemented due to community
safety concerns are “missing” or “broken”, and that volunteer-run servers maintaining control over who they allow and under what conditions are “exclusionary”. No consideration is
given to why the norms and affordances of Mastodon and the broader fediverse exist, and whether the actor they are designed to protect against might be you.
I genuinely believe that the fediverse is among our best bets for making a break from the silos of the corporate Web, and to do that it has to scale – it’s only the speed at which it’s
being asked to do so that’s problematic.
Aside from what I’m already doing – trying to tutor (tootor?) new fediversians about how to integrate in an appropriate and respectful manner and doing a little to supporting the
expansion of the software that makes it tick… I wonder what more I could/should be doing.
Would my effort be best-spent be running a server (one not-just-for-me, I mean: abnib.social, anyone?), or should I use that time and money to support existing instances
directly? Should I brush up on my ActivityPub spec so I can be a more-useful developer, or am I better-placed to focus on tending my own digital garden first? Or maybe I’m looking at it
all wrong and I should be trying to dissuade people from piling-on to a system that might well not be right for them (nor they for it!)?
I don’t know the answers to these questions, but I’m hoping to work them out soon.
Addendum
It only occurred to me after the fact that I should mention that you can find me at @dan@danq.me.
Footnotes
1 Important: I’m no expert. I’ve been doing fediverse things for about 3 years but I’m
relatively quiet on Mastodon. Also, I’ve never really understood or gotten along with Twitter, so I’m even less an expert on that. Don’t assume that I’m
an authority on anything at all, and especially not social media.
Your product, service, or organisation almost certainly has a priority of constituencies, even if it’s not written down or otherwise formally-encoded. A famous example would be that expressed in the Web Platform Design Principles. It dictates how you decide between two competing
needs, all other things being equal.
At Three Rings, for example, our priority of constituencies might1 look
like this:
The needs of volunteers are more important than
The needs of voluntary organisations, which are more important than
Continuation of the Three Rings service, which is more important than
Adherance to technical standards and best practice, which is more important than
Development of new features
These are all things we care about, but we’re talking about where we might choose to rank them, relative to one another.
The priorities and constituencies portrayed in this illustration are ficticious. Any resemblence to real priorities and constituencies, whether living or dead, is entirely
coincidental.
The priorities of an organisation you’re involved with won’t be the same: perhaps it includes shareholders, regulatory compliance, different kinds of end-users, employees, profits,
different measures of social good, or various measurable outputs. That’s fine: every system is different.
But what I’d challenge you to do is find ways to bisect your priorities. Invent scenarios that pit each constituency against itself another and discuss how they should
be prioritised, all other things being equal.
Using the example above, I might ask “which is more important?” in each category:
The needs of the volunteers developing Three Rings, or the needs of the volunteers who use it?
The needs of organisations that currently use the system, or the needs of organisations that are considering using it?
Achieving a high level of uptime, or promptly installing system updates?
Compliance with standards as-written, or maximum compatibility with devices as-used?
Implementation of new features that are the most popular user requests, or those which provide the biggest impact-to-effort payoff?
These might not be your answers to the same questions. They’re not even necessarily mine, and they’re even less-likely to be representative of Three Rings CIC. It’s just illustrative.
The aim of the exercise isn’t to come up with a set of commandments for your company. If you come up with something you can codify, that’s great, but if you and your stakeholders just
use it as an exercise in understanding the relative importance of different goals, that’s great too. Finding where people disagree is more-important than having a unifying
creed2.
And of course this exercise applicable to more than just organisational priorities. Use it for projects or standards. Use it for systems where you’re the only participant, as a thought
exercise. A priority of constituencies can be a beautiful thing, but you can understand it better if you’re willing to take it apart once in a while. Bisect your priorities, and see
what you find.
Footnotes
1 Three Rings doesn’t have an explicit priority of constituencies: the example I give is
based on my own interpretation, but I’m only a small part of the organisation.
I didn’t/don’t own much vinyl – perhaps mostly because I had a tape deck in my bedroom years before a record player – but I’ve felt this pain. And don’t get me started on the videogames I’ve paid for multiple times.
In the Summer of 1995 I bought the CD single of the (still excellent!) Set You
Free by N-Trance.2
I’d heard about this new-fangled “MP3” audio format, so soon afterwards I decided to rip a copy of the song to my PC.
I was using a 66MHz 486SX CPU, and without an embedded FPU I didn’t
quite have the spare processing power to rip-and-encode in a single pass.3
So instead I first ripped to an uncompressed PCM .wav file and then performed the encoding: the former step
was done almost in real-time (I listened to the track as it ripped!), about 7 minutes. The latter step took about 20 minutes.
So… about half an hour in total, to rip a single song.
Progress bar, you say? I’ll just sit here and wait then, I guess. Actual contemporary-ish photo.
Creating a (what would now be considered an apalling) 32kHz mono-channel file, this meant that I briefly stored both a 27MB wave file and the final ~4MB MP3 file. 31MB
might not sound huge, but I only had a total of 145MB of hard drive space at the time, so 31MB consumed over a fifth of my entire fixed storage! Even after deleting the intermediary wave file I was left with a single song consuming around 3% of my space,
which is mind-boggling to think about in hindsight.
But it felt like magic. I called my friend Gary to tell him about it. “This is going to be massive!” I said. At the time, I meant for techy
people: I could imagine a future in which, with more hard drive space, I’d keep all my music this way… or else bundle entire artists onto writable CDs in this new format, making albums obsolete. I never considered that over the coming decade or so the format would enter the public consciousness, let
alone that it’d take off like it did.
If you’re thinking of Gary and I as the kind of reprobates who helped bring on the golden age of music piracy… I’d like to distract you with a bigger show of yobbish behaviour in the
form of this photo from the day we played at dropping half-bricks onto starter pistol ammunition.
The MP3 file I produced had a fault. Most of the way through the encoding process, I got bored and ran another program, and this
must’ve interfered with the stream because there was an audible “blip” noise about 30 seconds from the end of the track. You’d have to be listening carefully to hear it, or else know
what you were looking for, but it was there. I didn’t want to go through the whole process again, so I left it.
But that artefact uniquely identified that copy of what was, in the end, a popular song to have in your digital music collection. As the years went by and I traded MP3 files in bulk at LAN parties or on CD-Rs or, on at least one ocassion, on an Iomega Zip disk (remember those?), I’d ocassionally see
N-Trance - (Only Love Can) Set You Free.mp34 being passed around and play it, to see if it was “my”
copy.
Sometimes the ID3 tags had been changed because for example the previous owner had decided it deserved to be considered Genre: Dance instead of Genre: Trance5. But I could still identify that file because
of the audio fingerprint, distinct to the first MP3 I ever created.
I still had that file when I went to university (where it occupied a smaller proportion of my hard drive space) and hearing that
distinctive “blip” would remind me about the ordeal that was involved in its creation. I don’t have it any more, but perhaps somebody else still does.
Footnotes
1 I might never have told this story on my blog, but eagle-eyed readers may remember that
I’ve certainly hinted at it before now.
2 Rewatching that music video, I’m struck by a recollection of how crazy popular
crossfades were on 1990s dance music videos. More than just a transition, I’m pretty sure that most of the frames of that video are mid-crossfade: it feels like I’m watching
Kelly Llorenna hanging out of a sunroof but I accidentally left one of my eyeballs in a smoky nightclub and can still see out of it as well.
3 I initially tried to convert directly from red book format to an MP3 file, but the encoding process was
too slow and the CD drive’s buffer filled up and didn’t get drained by the processor, which was still presumably bogged down with
framing or fourier-transforming earlier parts of the track. The CD drive reasonably assumed that it wasn’t actually being used and
spun-down the drive motor, and this caused it to lose its place in the track, killing the whole process and leaving me with about a 40 second recording.
4 Yes, that filename isn’t quite the correct title. I was wrong.
My day usually starts in my feed reader, accessed via the FeedMe app from my mobile (although FreshRSS provides a reasonably good
responsive interface out-of-the-box!)
But with FreshRSS 1.20.0, I no longer have to maintain my own tool to get this brilliant functionality, and I’m overjoyed. Let’s look at how it works by re-subscribing to Beverley’s
blog but without a middleware tool.
This post is about to get pretty technical. If you don’t want to learn some XPath but just want to make a feed out of a web page, use a
graphical tool like FetchRSS.
In the latest version of FreshRSS, when you add a new feed to your reader, a new section “Type of feed source” is available. Unfold it, and you can change from the default
(“RSS / Atom”) to the new option “HTML + XPath (Web scraping)”.
Put a human-readable page address rather than a feed address into the “Feed URL” field and fill these fields to tell FreshRSS
how to parse the page to get the content you want. Note that it doesn’t matter if the web page isn’t valid XML (e.g. missing
closing tags) because it’s going to get run through PHP’s
DOMDocument anyway which will “correct” for some really sloppy code if needed.
You can use your browser’s debugger to help check your XPath rules: here I’ve run document.evaluate('//li[@class="blog__post-preview"]', document).iterateNext() and
got back the first blog post on the page, so I know I’m on the right track.
You’ll need to use XPath to express how to find a “feed item” on the page. Here’s the rules I used for https://webdevbev.co.uk/blog.html (many of these fields were optional – I didn’t have to do this much work):
Feed title://h1
I override this anyway in FreshRSS, so I could just have used the a string, but I wanted the XPath practice. There’s only one <h1> on the page, and it can be
considered the “title” of the feed.
Finding items://li[@class="blog__post-preview"]
Each “post” on the page is an <li class="blog__post-preview">.
Item titles:descendant::h2
Each post has a <h2> which is the post title. The descendant:: selector scopes the search to each post as found above.
Item content:descendant::p[3]
Beverley’s static site generator template puts the post summary in the third paragraph of the <li>, which we can select like this.
Item link:descendant::h2/a/@href
This expects a URL, so we need the /@href to make sure we get the value of the <h2><a
href="...">, rather than its contents.
Item thumbnail:descendant::img[@class="blog__image--preview"]/@src
Again, this expects a URL, which we get from the <img src="...">.
Item author:"Beverley Newing"
Beverley’s blog doesn’t host any guest posts, so I just use a string literal here.
Item date:substring-after(descendant::p[@class="blog__date-posted"], "Date posted: ")
This is the only complicated one: the published dates on Beverley’s blog aren’t explicitly marked-up, but part of a string that begins with the words “Date posted: “, so I use XPath’s
substring-after function to strtip this. The result gets passed to PHP’s
strtotime(), which is pretty tolerant of different date formats (although not of the words “Date posted:” it turns out!).
I’d love one day for FreshRSS to provide some kind of “preview” feature here so you can see what you’ll expect to get back, as you work. That, and support for different input types
(JSON, perhaps?), perhaps other selectors (I find CSS-style
selectors much simpler than XPath), and maybe even an option to execute Javascript on the page before scraping (I use this in my own toolchain, but that’s just because I want to have
my cake and eat it too). But this is still all pretty awesome.
I hope that this is just the beginning for this new killer feature in FreshRSS: there’s so much more it can be and do. But for now, I’m still mighty impressed that I can begin to
phase-out my use of my relatively resource-intensive feed-building middleware and use my feed reader to do more and more of the heavy lifting for which I love it so much.
I also love that this functionally adds h-feed support in by the back door. I’d still prefer there to be a “h-feed” option in the “Type of feed source” drop-down, but at least
I can add such support manually, now!
The finished result: Bev’s blog posts appear directly in my feed reader, even though they don’t have a feed, and now without going through the middleware I’d set up for that
purpose.
Footnotes
1 When I say RSS, I mean feed. Most of the feeds I subscribe to are RSS feeds, but some
are Atom feeds, h-feed, etc. But I can’t get over the old-fashioned name, and I don’t care to try.
I managed to dodge infection for 922 days of the Covid pandemic1,
but it caught up with me eventually.
Well, shit.
Frankly, it’s surprising that it took this long. We’ve always been careful, in accordance with guidance at any given time, nd we all got our jabs and boosters as soon as we were able…
but conversely: we’ve got school-age children who naturally seem to be the biggest disease vectors imaginable. Our youngest, in fact, already had Covid, but the rest of us
managed to dodge it perhaps thanks to all these precautions.
The vaccine provide protection, but it’s not a magical force-field.
Luckily I’m not suffering too badly, probably thanks to the immunisation. It’s still not great, but I dread to think how it might have been without the benefit of the jab! A minor fever
came and went, and then it’s just been a few days of coughing, exhaustion, and… the most-incredible level of brain-fog.
Today, for example, I completey blanked the word “toilet” and struggled for some time to express to the dog why I’d brought her into the garden, while she stared at me expectantly.
I’ve taken the week off work to recover, which was a wise choice. As well as getting rest, it’s meant that I’ve managed to avoid writing production code with my addled brain! Instead,
I’ve spent a lot of time chilling in bed and watching all of the films that I’d been meaning to! This week, I’ve watched:
Peggy Sue Got Married (y’know, that other mid-1980s movie about time travel and being a teenager in the 1950s). It was okay; some bits of the direction were
spectacular for its age, like the “through the mirror” filming.
Fall. I enjoyed this more than I expected to. It’s not great, but while I spent most of the time complaining about the
lack of believability in the setting and the characters’ reactions, the acting was good and the tension “worked”: it was ocassionally pretty vertigo-inducing, and that’s not just
because I’ve been having some Covid-related dizziness!
RRR. Oh my god this Tollywood action spectacle was an adventure. At one point it’s a bromantic buddy comedy, then later
there’s a dance-off, then for a while there’s a wonderful “even language can’t divide us” romance, but then later a man picks up a motorcycle with one hand and uses it to beat up an
entire army, and somehow it all feels like it belongs together. The symbolism’s so thick you can spread it (tl;dr: colonialism
bad), but it’s still a riot of a film.
Cyrano, which I feel was under-rated but that could just be that I have a soft spot for the story… and a love of musical
theatre.
Also, at times when I didn’t think my brain had the focus for something new, I re-watched Dude, Where’s My Car? because
I figured a stoner comedy that re-replains the plot every 20 minutes or so was about as good as I could expect my brain to handle at the time, and Everything Everywhere All At Once which I’ve now seen three times and loved every single one: it’s one of my favourite films.
See, I’m fine! (Feel like I’ve spent a lot of time lying here, this week.)
Anyway: hopefully next week I’ll be feeling more normal and my poor Covid-struck brain can be trusted with code again. Until then: time to try to rest some more.
Footnotes
1 Based on the World Health Organisation’s declaration of the outbreak being a pandemic on
11 March 2020 and my positive test on 19 September 2022, I stayed uninfected for two years, six months, one week, and one day. But who’s counting?
That’s a really useful thing to have in this new age of the web, where Refererer: headers are no-longer commonly passed cross-domain and Google Search no longer provides the link: operator. If you want to know if I’ve ever
linked to your site, it’s a bit of a drag to find out.
To nobody’s surprise whatsoever, I’ve made a so many links to Wikipedia that I might be single-handedly responsible for their PageRank.
So, obviously, I’ve written an implementation for WordPress. It’s really basic right now, but the source code can be
found here if you want it. Install it as a plugin and run wp outbound-links to kick it off. It’s fast: it takes 3-5 seconds to parse the entirety of danq.me,
and I’ve got somewhere in the region of 5,000 posts to parse.
You can see the results at https://danq.me/.well-known/links – if you’ve ever wondered “has Dan ever linked to my site?”, now you can find the
answer.
If this could be useful to you, let’s collaborate on making this into an actually-useful plugin! Otherwise it’ll just languish “as-is”, which is good enough for my purposes.
I swear that I used to be good at Mastermind when I was a kid. But now, when it’s my turn to break
the code that one of our kids has chosen, I fail more often than I succeed. That’s no good!
If you didn’t have me pegged as a board gamer… where the hell have you been?
Mastermind and me
Maybe it’s because I’m distracted; multitasking doesn’t help problem-solving. Or it’s because we’re “Super” Mastermind, which differs from the one I had as a child in that
eight (not six) peg colours are available and secret codes are permitted to have duplicate peg colours. These changes increase the possible permutations from 360 to 4,096, but the
number of guesses allowed only goes up from 8 to 10. That’s hard.
The set I had as a kid was like this, I think. Photo courtesy ZeroOne; CC-BY-SA license.
Hey, that’s an idea. Let’s crack the code… by writing some code!
This online edition plays a lot like the version our kids play, although the peg colours are different. Next guess should be an
easy solve!
Representing a search space
The search space for Super Mastermind isn’t enormous, and it lends itself to some highly-efficient computerised storage.
There are 8 different colours of peg. We can express these colours as a number between 0 and 7, in three bits of binary, like this:
Decimal
Binary
Colour
0
000
Red
1
001
Orange
2
010
Yellow
3
011
Green
4
100
Blue
5
101
Pink
6
110
Purple
7
111
White
There are four pegs in a row, so we can express any given combination of coloured pegs as a 12-bit binary number. E.g. 100 110 111 010 would represent the
permutation blue (100), purple (110), white (111), yellow (010). The total search space, therefore, is the range of numbers from
000000000000 through 111111111111… that is: decimal 0 through 4,095:
Decimal
Binary
Colours
0
000000000000
Red, red, red, red
1
000000000001
Red, red, red, orange
2
000000000010
Red, red, red, yellow
…………
4092
111111111100
White, white, white, blue
4093
111111111101
White, white, white, pink
4094
111111111110
White, white, white, purple
4095
111111111111
White, white, white, white
Whenever we make a guess, we get feedback in the form of two variables: each peg that is in the right place is a bull; each that represents a peg in the secret code but
isn’t in the right place is a cow (the names come from Mastermind’s precursor, Bulls & Cows). Four bulls
would be an immediate win (lucky!), any other combination of bulls and cows is still valuable information. Even a zero-score guess is valuable- potentially very valuable! – because it
tells the player that none of the pegs they’ve guessed appear in the secret code.
If one of Wordle‘s parents was Scrabble, then this was the other. Just ask its Auntie Twitter.
Solving with Javascript
The latest versions of Javascript support binary literals and bitwise operations, so we can encode and decode between arrays of four coloured pegs (numbers 0-7) and the number 0-4,095
representing the guess as shown below. Decoding uses an AND bitmask to filter to the requisite digits then divides by the order of magnitude. Encoding is just a reduce
function that bitshift-concatenates the numbers together.
/** * Decode a candidate into four peg values by using binary bitwise operations. */function decodeCandidate(candidate){
return [
(candidate &0b111000000000) /0b001000000000,
(candidate &0b000111000000) /0b000001000000,
(candidate &0b000000111000) /0b000000001000,
(candidate &0b000000000111) /0b000000000001
];
}
/** * Given an array of four integers (0-7) to represent the pegs, in order, returns a single-number * candidate representation. */function encodeCandidate(pegs) {
return pegs.reduce((a, b)=>(a <<3) + b);
}
With this, we can simply:
Produce a list of candidate solutions (an array containing numbers 0 through 4,095).
Choose one candidate, use it as a guess, and ask the code-maker how it scores.
Eliminate from the candidate solutions list all solutions that would not score the same number of bulls and cows for the guess that was made.
Repeat from step #2 until you win.
Step 3’s the most important one there. Given a function getScore( solution, guess ) which returns an array of [ bulls, cows ] a given guess would
score if faced with a specific solution, that code would look like this (I’m convined there must be a more-performant way to eliminate candidates from the list with XOR
bitmasks, but I haven’t worked out what it is yet):
/** * Given a guess (array of four integers from 0-7 to represent the pegs, in order) and the number * of bulls (number of pegs in the guess that are in the right place) and cows (number of pegs in the * guess that are correct but in the wrong place), eliminates from the candidates array all guesses * invalidated by this result. Return true if successful, false otherwise. */function eliminateCandidates(guess, bulls, cows){
const newCandidatesList = data.candidates.filter(candidate=>{
const score = getScore(candidate, guess);
return (score[0] == bulls) && (score[1] == cows);
});
if(newCandidatesList.length ==0) {
alert('That response would reduce the candidate list to zero.');
returnfalse;
}
data.candidates = newCandidatesList;
chooseNextGuess();
returntrue;
}
I continued in this fashion to write a full solution (source code). It uses ReefJS for
component rendering and state management, and you can try it for yourself right in your web browser. If you play against the online version I mentioned you’ll need to transpose the colours in your head: the physical version I play with the kids has pink and
purple pegs, but the online one replaces these with brown and black.
Testing the solution
Let’s try it out against the online version:
As expected, my code works well-enough to win the game every time I’ve tried, both against computerised and in-person opponents. So – unless you’ve been actively thinking about the
specifics of the algorithm I’ve employed – it might surprise you to discover that… my solution is very-much a suboptimal one!
My code has only failed to win a single game… and that turned out to because my opponent, playing overexcitedly, cheated in the third turn. To be fair, my code didn’t lose
either, though: it identified that a mistake must have been made and we declared the round void when we identified the problem.
My solution is suboptimal
A couple of games in, the suboptimality of my solution became pretty visible. Sure, it still won every game, but it was a blunt instrument, and anybody who’s seriously thought about
games like this can tell you why. You know how when you play e.g. Wordle (but not in “hard mode”) you sometimes want to type in a word that can’t possibly be the
solution because it’s the best way to rule in (or out) certain key letters? This kind of strategic search space bisection reduces the mean number of guesses you need to solve the
puzzle, and the same’s true in Mastermind. But because my solver will only propose guesses from the list of candidate solutions, it can’t make this kind of improvement.
My blog post about Break Into Us used a series of visual metaphors to show search space dissection, including this one. If you missed
it, it might be worth reading.
Search space bisection is also used in my adverserial hangman game, but in this case the aim is to split the search space in such a way that no
matter what guess a player makes, they always find themselves in the larger remaining portion of the search space, to maximise the number of guesses they have to make. Y’know, because
it’s evil.
A great first guess, assuming you’re playing against a random code and your rules permit the code to have repeated colours, is a “1122” pattern.
There are mathematically-derived heuristics to optimise Mastermind strategy. The first
of these came from none other than Donald Knuth (legend of computer science, mathematics, and pipe organs) back in 1977. His solution,
published at probably the height of the game’s popularity in the amazingly-named Journal of Recreational Mathematics, guarantees a solution to the six-colour version of the
game within five guesses. Ville [2013] solved an
optimal solution for a seven-colour variant, but demonstrated how rapidly the tree of possible moves grows and the need for early pruning – even with powerful modern computers – to
conserve memory. It’s a very enjoyable and readable paper.
But for my purposes, it’s unnecessary. My solver routinely wins within six, maybe seven guesses, and by nonchalantly glancing at my phone in-between my guesses I can now reliably guess
our children’s codes quickly and easily. In the end, that’s what this was all about.
My mum has made pets of one or both of dogs or cats for most of her life. She puts the difference between the two in a way that really resonates for me. To paraphrase her:
When you’re feeling down and you’ve had a shitty day and you just need to wallow in your despair for a little bit… a pet dog will try to cheer you up. It’ll jump up at you, bring
you toys, suggest that you go for a walk, try to pull your focus away from your misery and bring a smile to your face. A cat, though, will just come and sit and be melancholy with you.
Its demeanour just wordlessly says: “You’re feeling crap? Me too: I only slept 16 hours today. Let’s feel crap together.”
“I hate Mondays. Also any other day of the week with a ‘Y’ in it.”
So it surprised many when, earlier this year, our family was expanded with the addition of a puppy called Demmy. I guess we collectively figured that now we’d solved all the hard
problems and the complexities of our work, volunteering, parenting, relationships, money etc. and our lives were completely simple, plain sailing, and stress-free, all of the time… that
we now had the capacity to handle adding another tiny creature into our midst. Do you see the mistake in that logic? Maybe we should have, too.
The kids were, and continue to be, absolutely delighted, especially our eldest who’s been mad about dogs now for well over half her life.
It turns out that getting a puppy is a lot like having a toddler all over again. Your life adjusts around when they need
to sleep, eat, and poop. You need to put time, effort, and thought into how to make and keep your house safe both for and from them. And, of course, they bring with
them a black hole that eats disposable income.
Sure they’re cute when they’re asleep, but the rest of the time they’re probably destroying things, pooping, or both. #PuppyOrToddler?
They need to be supervised and entertained and educated (the latter of which may require some education yourself). They need to be socialised so they can interact nicely with others,
learn the boundaries of their little world, and behave appropriately (even when they’re noton camera).
At the end of elementary “puppy school”, we tried some agility course obstacles. Jumps were a success, even for Demmy’s little legs, but she’d far rather hang out inside a tunnel than
run through one.
Even as they grow, their impact is significant. You need to think more-deeply about how, when and where you travel, work out who’s responsible for ensuring they’re walked (or carried!) and fed (not eaten!) and watched. You’ve got to keep them safe and healthy and stimulated.
Thankfully they’re not as tiring to play with as children, but as with kids, the level of effort required is hard to anticipate until you
have one.
Whether you’re a human pup or a canine pup, there’s fun to be had in leaping out of long grass to pounce one another.
But do you know what else they have in common with kids? You can’t help learning to love them.
It doesn’t matter what stupid thing they’re illicitly putting in their mouth, how many times you have to clean up after them, how frustrating it is that they can’t understand what you
need from them in order to help them, or how much they whine about something that really isn’t that big a deal (again: #PuppyOrToddler?). It doesn’t even matter how much you’re “not a
dog person”, whatever that means. They become part of your family, and you fall in love with them.
Panting and too hot from a long run under the hot summer sun, but loving the opportunity to get out and enjoy the sights and smells of the world. #PuppyOrDan?
I’m not a “dog person”. But: while I ocassionally resent the trouble she causes, I still love our dog.
I’ve been playing with a Flic Hub LR and some Flic 2 buttons. They’re “smart home” buttons, but for me they’ve got a killer selling point: rather than
locking you in to any particular cloud provider (although you can do this if you want), you can directly program the hub. This means you can produce smart integrations
that run completely within the walls of your house.
Here’s some things I’ve been building:
Prerequisite: Flic Hub to Huginn connection
Step 1. Enable SDK access. Check!
I run a Huginn instance on our household NAS. If
you’ve not come across it before, Huginn is a bit like an open-source IFTTT: it’s got a steep
learning curve, but it’s incredibly powerful for automation tasks. The first step, then, was to set up my Flic Hub LR to talk to Huginn.
Checking ‘Restart after crash’ seems to help ensure that the script re-launches after e.g. a power cut. Need
the script?
This was pretty simple: all I had to do was switch on “Hub SDK web access open” for the hub using the Flic app,
then use the the web SDK to add this script to the hub. Now whenever a
button was clicked, double-clicked, or held down, my Huginn installation would receive a webhook ping.
Depending on what you have Huginn do “next”, this kind of set-up works completely independently of “the cloud”. (Your raven can fly into the clouds if you really want.)
For convenience, I have all button-presses sent to the same Webhook, and use Trigger Agents to differentiate between buttons and press-types. This means I can re-use
functionality within Huginn, e.g. having both a button press and some other input trigger a particular action.
You’ve Got Mail!
By our front door, we have “in trays” for each of Ruth, JTA and I, as well as one for the
bits of Three Rings‘ post that come to our house. Sometimes post sits in the in-trays for a long time because people don’t think to check
them, or don’t know that something new’s been added.
I configured Huginn with a Trigger Agent to receive events from my webhook and filter down to just single clicks on specific buttons. The events emitted by these triggers are used to
notify in-tray owners.
Once you’ve made three events for your first button, you can copy-paste from then on.
In my case, I’ve got pings being sent to mail recipients via Slack, but I could equally well be integrating to other (or additional) endpoints or even
performing some conditional logic: e.g. if it’s during normal waking hours, send a Pushbullet notification to the recipient’s phone, otherwise
send a message to an Arduino to turn on an LED strip along the top of the recipient’s in-tray.
I’m keeping it simple for now. I track three kinds of events (click = “post in your in-tray”, double-click = “I’ve cleared my in-tray”, hold = “parcel wouldn’t fit in your in-tray: look
elsewhere for it”) and don’t do anything smarter than send notifications. But I think it’d be interesting to e.g. have a counter running so I could get a daily reminder (“There
are 4 items in your in-tray.”) if I don’t touch them for a while, or something?
Remember the Milk!
Following the same principle, and with the hope that the Flic buttons are weatherproof enough to work in a covered outdoor area, I’ve fitted one… to the top of the box our milkman
delivers our milk into!
The handle on the box was almost exactly the right size to stick a Flic button to! But it wasn’t flat enough until I took a file to it.
Most mornings, our milkman arrives by 7am, three times a week. But some mornings he’s later – sometimes as late as 10:30am, in extreme cases. If he comes during the school run the milk
often gets forgotten until much later in the day, and with the current weather that puts it at risk of spoiling. Ironically, the box we use to help keep the milk cooler for longer on
the doorstep works against us because it makes the freshly-delivered bottles less-visible.
Now that I had the technical infrastructure already in place, honestly the hardest part of this project was matching the font used in Milk &
More‘s logo.
I’m yet to see if the milkman will play along and press the button when he drops off the milk, but if he does: we’re set! A second possible bonus is that the kids love doing anything
that allows them to press a button at the end of it, so I’m optimistic they’ll be more-willing to add “bring in the milk” to their chore lists if they get to double-click the button to
say it’s been done!
Future Plans
I’m still playing with ideas for the next round of buttons. Could I set something up to streamline my work status, so my colleagues know when I’m not to be disturbed, away from my desk,
or similar? Is there anything I can do to simplify online tabletop roleplaying games, e.g. by giving myself a desktop “next combat turn” button?
My Flic Hub is mounted behind a bookshelf in the living room, with only its infrared transceiver exposed. 20p for scale: we don’t keep a 20p piece stuck to the side of the bookcase
all the time.
I’m quite excited by the fact that the Flic Hub can interact with an infrared transceiver, allowing it to control televisions and similar devices: I’d love to be able to use the volume
controls on our media centre PC’s keyboard to control our TV’s soundbar: and because the Flic Hub can listen for UDP packets, I’m hopeful that something as simple as AutoHotkey can make this possible.
Or perhaps I could make a “universal remote” for our house, accessible as a mobile web app on our internal Intranet, for those occasions when you can’t even be bothered to stand up to
pick up the remote from the other sofa. Or something that switched the TV back to the media centre’s AV input when consoles were powered-down, detected by their network activity? (Right
now the TV automatically switches to the consoles when they’re powered-on, but not back again afterwards, and it bugs me!)
It feels like the only limit with these buttons is my imagination, and that’s awesome.
Today I wanted to write a script that I could execute from both *nix (using Bash or a similar shell) and on Windows (from a command prompt, i.e. a batch file). I found Max Norin’s solution which works, but has a few limitations, e.g. when run it outputs either the
word “off” when run in *nix or the word “goto” when run on Windows. There’s got to be a sneaky solution, right?
Here’s my improved version:
1
2
3
4
5
6
7
8
9
10
11
12
@goto(){
# Linux code here
uname -o
}
@goto $@
exit
:(){
@echooffrem Windows script hereecho%OS%
Mine exploits the fact that batch files can prefix commands with @ to suppress outputting them as they execute. So @goto can be a valid function name in
bash/zsh etc. but is interpreted as a literal goto command in a Windows Command Prompt. This allows me to move the echo off command –
which only has meaning to Windows – into the Windows section of the script and suppress it with @.
The file above can be saved as e.g. myfile.cmd and will execute in a Windows Command Prompt (or in MS-DOS) or your favourite *nix OS. Works in MacOS/BSD too, although
obviously any more-sophisticated script is going to have to start working around the differences between GNU and non-GNU versions of core utilities, which is always a bit of a pain!
Won’t work in sh because you can’t define functions like that.
But the short of it is you can run this on a stock *nix OS and get:
$ ./myfile.cmd
GNU/Linux
And you can run it on Windows and get:
> .\myfile.cmd
Windows_NT
You can’t put a shebang at the top because Windows hates it, but there might be a solution using PowerShell scripts (which use
hashes for comments: that’s worth thinking about!). In any case: if the interpreter strictly matters you’ll probably want to shell to it on line 3 with e.g. bash -c.
Why would you want such a thing? I’m not sure. But there is is, if you do.
There are a great number of things that I’m bad at. One thing I’m bad at (but that I’m trying to get better at) is being more-accepting of the fact that there are things that I am bad
at.
I’ve also been thinking about how I’m bad at thinking about how I’m bad at thinking about how I’m bad at thinking about…
I’m also particularly bad at choosing suitable stock photos for use in blog posts.
Being Bad
As a young kid, I was a smart cookie. I benefited from being an only child and getting lots of attention from a pair of clever parents, but I was also pretty bright and a quick learner
with an interest in just about anything I tried. This made me appear naturally talented at a great many things, and – pushed-on by the praise of teachers, peers, and others – I
discovered that I could “coast” pretty easily.
But a flair for things will only carry you so far, and a problem with not having to work hard at your education means that you don’t learn how to learn. I got bitten
by this when I was in higher education, when I found that I actually had to work at getting new information to stick in my head (of course, being older makes learning harder
too, as became especially obvious to me during my most-recent qualification)!
Ignore the fact that you’ve now seen me trying to sledge uphill and just accept that I was a clever kid (except at
photography), okay?
A side-effect of these formative experiences is that I grew into an adult who strongly differentiated between two distinct classes of activities:
Things I was good at, either because of talent or because I’d thoroughly studied them already. I experienced people’s admiration and respect when I practised these
things, and it took little effort to stay “on top” of these fields, and
Things I was bad at, because I didn’t have a natural aptitude and hadn’t yet put the time in to learning them. We don’t often give adults external
reinforcement for “trying hard”, and I’d become somewhat addicted to being seen as awesome… so I shied away from things I was “bad at”.
The net result: I missed out on opportunities to learn new things, simply because I didn’t want to be seen as going through the “amateur” phase. In hindsight, that’s
really disappointing! And this “I’m bad at (new) things” attitude definitely fed into the imposter syndrome I felt when I first
started at Automattic.
Being Better
Leaving the Bodleian after 8½ years might have helped stimulate a change in me. I’d carved out a role for myself defined by the fields I knew
best; advancing my career would require that I could learn new things. But beyond that, I benefited from my new employer whose “creed
culture” strongly promotes continuous learning (I’ve vlogged about this before), and from my coach who’s been great at encouraging me towards a growth mindset.
“Good Luck Dan”, my Bodleian buddies said. But perhaps they should’ve said “Keep Learning Dan”.
But perhaps the biggest stimulus to remind me to keep actively learning, even (especially?) when it’s hard, might have been the pandemic. Going slightly crazy with cabin fever during
the second lockdown, I decided to try and teach myself how to play the piano. Turns out I wasn’t alone, as I’ve mentioned before: the pandemic did strange things to us all.
I have no real experience of music; I didn’t even get to play recorder in primary school. And I’ve certainly got no talent for it (I can hear well enough to tell how awful my
singing is, but that’s more a curse than a blessing). Also, every single beginners’ book and video course I looked at starts from the assumption that you’re going to want to “feel” your
way into it, and that just didn’t sit well with the way my brain works.
90% of what I do in front of a piano might be described as “Dan Mucks About (in B Minor)”, but that’s fine by me.
I wanted a theoretical background before I even sat down at a keyboard, so I took a free online course in music theory. Then I started working through a
“beginners’ piano” book we got for the kids. Then I graduated to “first 50 Disney songs”, because I know how virtually all of them sound well enough that I’d be able to hear where I was
going wrong. Since then, I’ve started gradually making my way through a transcription of Einaudi’s Islands. Feeling like I’d got a good handle on what I was supposed to be
doing, I then took inspiration from a book JTA gave me and started trying to improvise.
Most days, I get no more than about 10 minutes on the piano. But little by little, day by day, that’s enough to learn. Nowadays even my inner critic perfectionist can
tolerate hearing myself play. And while I know that I’ll probably never be as good as, say, the average 8-year-old on YouTube, I’m content in my limited capacity.
Let’s start at the very beginning. (A very good place to start.)
If I’m trying to cultivate my wonder syndrome, I need to stay alert for “things I’m bad at” that I could conceivably be better at if
I were just brave enough to try to learn. I’m now proudly an “embarrassingly amateur” pianist, which I’m at-long-last growing to see as better than a being non-pianist.
Off the back of that experience, I’m going to try to spend more time doing things that I’m bad at. And I’d encourage you to do the same.
I love my electric car, but sometimes – like when I need to transport five people and a week’s worth of their luggage 250 miles and need to get there before the kids’ bedtime! – I still
use our big ol’ diesel-burning beast. And it was while preparing for such a journey that I recently got to thinking about the mathematics of refuelling.
I don’t know why you’d measure range in millilitres in the first place, but I’m hearing that I ought to fill up the car before we go.
It’s rarely worth travelling out-of-your-way to get the best fuel
prices. But when you’re on a long road trip anyway and you’re likely to pass dozens of filling stations as a matter of course, you might as well think at least a
little about pulling over at the cheapest.
You could use one of the many online services to help with this, of course… but assuming you didn’t do this and you’re already on the road, is there a better strategy than just trusting
your gut and saying “that’s good value!” when you see a good price?
If you can’t decide which nozzle is best, mix up a cocktail of them all. #TerribleProTip
Here’s how you do it:
Estimate your outstanding range R: how much further can you go? Your car might be able to help you with this. Let’s say we’ve got 82 miles in the tank.
Estimate the average distance between filling stations on your route, D. You can do this as-you-go by counting them over a fixed distance and continue from
step #4 as you do so, and it’ll only really mess you up if there are very few. Maybe we’re on a big trunk road and there’s a filling station about every 5 miles.
Divide R by D to get F: the number of filling stations you expect to pass before you completely run out of fuel. Round down,
obviously, unless you’re happy to push your vehicle to the “next” one when it breaks down. In our example above, that gives us 16 filling stations we’ll probably see before we’re
stranded.
Divide F by e to get T (use e = 2.72 if you’re having to do this in your head). Round down again, for the same reason as before.
This gives us T=5.
Drive past the next T filling stations and remember the lowest price you see. Don’t stop for fuel at any of these.
Keep driving, and stop at the first filling station where the fuel is the same price or cheaper than the cheapest you’ve seen so far.
Obviously you should take care doing maths on the road. Don’t drink and derive!
This is a modified variant of the Secretary Problem because it’s possible for two filling stations to have the same price, and that’s reflected in the algorithm above by the
allowance for stopping for fuel at the same price as the best you saw during your sampling phase. It’s probably preferable to purchase sub-optimally than to run completely dry, right?
Of course, you’re still never guaranteed a good solution with this approach, but it maximises your odds. Your own risk-assessment might rank “not breaking down” over pure mathematical
efficiency, and that’s on you.
Ruth wrote an excellent post this month entitled Wonder Syndrome.
It attempts to reframe imposter syndrome (which is strongly, perhaps disproportionately, present in tech fields) as a
positive indicator that there’s still more to learn:
Being aware of the boundaries of our knowledge doesn’t make us imposters, it makes us explorers. I’m going to start calling mine “Wonder Syndrome”, and allowing myself to be awed by
how much I still have to learn, and then focusing in and carrying on with what I’m doing because although I may not reach the stars, I’ve come a long way up the mountain. I can learn
these things, I can solve these problems, and I will.
I don’t recall exactly what I’m advising a fellow Three Rings developer to do, here, but I don’t think he’s happy about it.
I just spent a week at a Three Rings DCamp (a “hackathon”, kinda), and for the umpteenth time had the experience of feeling like
everybody thinks I know everything, while on the inside I still feel like I’m still guessing a third of the time (and on StackOverflow for another third!).
The same’s true at work: people ask me questions about things that I suppose, objectively, are my “specialist subjects” – web standards, application security, progressive enhancement,
VAT for some reason – and even where I’m able to help, I often get that nagging feeling like
there must be somebody better than me they could have gone to?
You’ve probably seen diagrams like this before. After all: I’m not smart or talented enough to invent anything like this and I don’t know why you’d listen to anything I have to say on
the subject anyway. 😂
You might assume that I love Ruth’s post principally because it plays to my vanity. The post describes two kinds of knowledgeable developers, who are differentiated primarily by their
attitude to learning. One is satisfied with the niche they’ve carved out for themselves and the status that comes with it and are content to rest on their laurels; the other is driven
to keep pushing and learning more and always hungry for the next opportunity to grow. And the latter category… Ruth’s named after me.
Wait, what if I’m not‽ Have I been faking it this entire blog post?
Bnd while I love the post, my gut feeling to being named after such an ideal actually makes me slightly uncomfortable. The specific sentence that gets me is (emphasis mine):
Dans have no interest in being better than other people, they just want to know more than they did yesterday.
I wish that was me, but I’m actually moderately-strongly motivated by a desire to feel like I’m the smartest person in the room! I’m getting this urge under control (I’m pretty
sure I was intolerable as a child and have been improving by instalments since then!). Firstly, because it’s an antisocial pattern to foster, but also because it limits my ability to
learn new things to have to go through the awkward, mistake-filled “I’m a complete amateur at this!” phase. But even as I work on this I still get that niggling urge, more often than
I’d like, to “show off”.
Of course, it could well be that what I’m doing right now is catastrophising. I’m taking a nice thing somebody’s said about me, picking the one part of it that I find hardest to feel
represents me, and deciding that I must be a fraud. Soo… imposter syndrome, I guess. Damn.
Or to put it a better way: Wonder Syndrome. I guess this is another area for self-improvement.
(I’m definitely adopting Wonder Syndrome into my vocabulary, as an exercise in mitigating imposter syndrome. If you’ve not read Ruth’s post in full, you should go and do that next.)
Taking a photo of our kids isn’t too hard: their fascination with screens means you just have to switch to “selfie mode” and they lock-on to the camera like some kind of narcissist
homing pigeon. Failing that, it’s easy enough to distract them with something that gets them to stay still for a few seconds and not just come out as a blur.
“On the school run” probably isn’t a typical excuse for a selfie, but the light was good.
But compared to the generation that came before us, we have it really easy. When I was younger than our youngest is , I was obsessed with pressing buttons. So pronounced was my
fascination that we had countless photos, as a child, of my face pressed so close to the lens that it’s impossible for the camera to focus, because I’d rushed over at the last second to
try to be the one to push the shutter release button. I guess I just wanted to “help”?
Oh wait… is there something on that camera I can press?
In theory, exploiting this enthusiasm should have worked out well: my parents figured that if they just put me behind the camera, I could be persuaded to take a good picture
of others. Unfortunately, I’d already fixated on another aspect of the photography experience: the photographer’s stance.
When people were taking picture of me, I’d clearly noticed that, in order to bring themselves down to my height (which was especially important given that I’d imminently try to
be as close to the photographer as possible!) I’d usually see people crouching to take photos. And I must have internalised this, because I started doing it too.
Another fantastic photo by young Dan: this one shows around 80% of my mum’s face and around 100% of my dad’s manspreading.
Unfortunately, because I was shorter than most of my subjects, this resulted in some terrible framing, for example slicing off the tops of their heads or worse. And because this was a
pre-digital age, there was no way to be sure exactly how badly I’d mucked-up the shot until days or weeks later when the film would be developed.
I imagine that my dad hoped to see more of whatever bus that is, in this photo, but he’s probably just grateful that I didn’t crop off any parts of his body this time.
In an effort to counteract this framing issue, my dad (who was always keen for his young assistant to snap pictures of him alongside whatever article of public transport history he was
most-interested in that day) at some point started crouching himself in photos. Presumably it proved easier to just duck when I did rather than to try to persuade me not to crouch in
the first place.
As you look forward in time through these old family photos, though, you can spot the moment at which I learned to use a viewfinder, because people’s heads start to feature close to the
middle of pictures.
This is a “transitional period” photo, evidenced by the face that my dad is clearly thinking about whether or not he needs to crouch.
Unfortunately, because I was still shorter than my subjects (especially if I was also crouching!), framing photos such that the subject’s face was in the middle of the frame resulted in
a lot of sky in the pictures. Also, as you’ve doubtless seem above, I was completely incapable of levelling the horizon.
This is the oldest photo I can find that was independently taken by our youngest child, then aged 3. I’m the subject, and I’m too close to the lens, blurred because I’m in motion, and
clearly on my way to try to “help” the photographer. Our ages might as well be reversed.
I’d like to think I’ve gotten better since, but based on the photo above… maybe the problem has been me, all along!
For lunch today I taste-tested five different plant-based vegan “cheeses” from Honestly Tasty. Let’s see if they’re any good.
Prefer video?
This blog post is available as a video (here or on YouTube), for those who like that sort of thing. The content’s slightly different, but you
do get to see my face when I eat the one that doesn’t agree with me.
Background
I’ve been vegetarian or mostly-vegetarian to some degree or another for a little over ten years (for
those who have trouble keeping up: I currently eat meat only on weekends, and not including beef or lamb), principally for the environmental benefits of a reduced-meat lifestyle. But if
I’m really committed to reducing the environmental impact of my diet, the next “big” thing I still consume is dairy products.
My milk consumption is very low nowadays, but – like many people who might aspire towards dropping dairy – it’s quitting cheese that poses the biggest challenge. I’m not even the
biggest fan of cheese, and I don’t know how I’d do without it: there’s just, it seems, no satisfactory substitute.
It’s possible, though, that my thinking on this is outdated. Especially in recent years, we’re getting better and better at making convincing (or, at least, tasty!) plant-based
substitutes to animal based foods. And so, inspired by a conversation with some friends, I thought I’d try a handful of new-generation plant-based cheeses and see how I got on. I
ordered a variety pack from Honestly Tasty (who’ll give you 20% off your first order if you subscribe your favourite throwaway email address to
their newsletter) and gave it a go.
It’s supposed to taste like Brie, I guess, but it’s not convincing. The texture of the rind is surprisingly good, but the inside is somewhat homogeneous and flat. They’ve tried to use
mustard powder to provide Brie’s pepperiness and acetic acid for its subtle sourness, but it feels like there might be too much of the former (or perhaps I’m just a little oversensitive
to mustard) and too little of the latter. It’s okay, but I wouldn’t buy it again.
This was surprisingly flavoursome and really quite enjoyable. It spreads with about the consistency of pâté and has a sharp tang that really stands out. You wouldn’t mistake it for
cheese, but you might mistake it for a cheese spread: there’s a real “cheddary” flavour buried in there.
This is supposed to be modelled after Gorgonzola, and it might as well be because I don’t like either it or the cheese it’s based on. I might loathe Blue slightly less than
most blue cheeses, but that doesn’t mean I’d willingly subject myself to this again in a hurry. It’s matured with real Penicillium Roqueforti, apparently, along with seaweed, and it
tastes like both of these things are true. So yeah: I hated this one, but you shouldn’t take that as a condemnation of its quality as a cheese substitute because I’d still rather eat it
than the cheese that it’s based on. Try for yourself, I guess.
This was probably my favourite of the bunch. It’s reminiscent of garlic & herb Boursin, and feels like somebody in the
kitchen where they cooked it up said to themselves, “how about we do the Ched Spread, but with less onion and a whole load of herbs mixed-through”. It seems that it must be easier to
make convincingly-cheesy soft cheeses than hard cheeses, but I’m not complaining: this would be great on toast.
If you’d served me this and told me it was a baked Camembert… I wouldn’t be fooled. But I wouldn’t be disappointed either. It moves a lot like Camembert and it tastes… somewhat like it.
But whether or not that’s “enough” for you, it’s perfectly delicious and I’d be more than happy to eat it or serve it to others.
In summary…
Honestly Tasty’s Ched Spread, Herbi, and Shamembert are perfectly acceptable (vegan!) substitutes for cheese. Even where they don’t accurately reflect the cheese they attempt to model,
they’re still pretty good if you take them on their own merits: instead of comparing them to their counterparts, consider each as if it were a cheese spread or soft cheese in its own
right and enjoy accordingly. I’d buy them again.
Their Bree failed to capture the essence of a good ripe Brie and its flavour profile wasn’t for me something to enjoy outside of its attempts at emulation. And their “Veganzola” Blue
cheese… was pretty grim, but then that’s what I think of Gorgonzola too, so maybe it’s perfect and I just haven’t the palate for it.