HN user

ojkelly

662 karma

https://ojkelly.dev

Side projects https://projects.owenkelly.com.au

Posts11
Comments195
View on HN

I bake all my own bread, mostly due to food allergies but also because there is nothing better than a fresh loaf of bread just out of the oven.

My experience and conclusion was largely the same as the author. I found the brevity of many recipes frustrating, which led me to study the details of bread making.

The type of four, how much yeast, salt and water. How the water amount varies for so many reasons in a home kitchen.

Making 4 loafs a fortnight gives plenty of opportunity for iteration and learning. And over time I was able to develop a good understanding of dough hydration and how that affects the end product, and how it can make kneading and handling the dough easier or harder. There is a sweet spot.

This also led me to adding oil to a dough. Which acts to keep the gluten chains from getting too long and chewy, thus making the dough short.

It’s also how you make a great pizza dough.

From there I started experimenting with different flours, because I had to if I wanted bread. Running low on bread flour I added semolina, and also made a pure semolina loaf.

The main thing I’ve learned is there is a lot of leeway in the recipes that still produces tasty food. I’ve made some incredible and some woeful loafs that all taste great.

I still prefer specific recipes in metric, but I find more and more I’m reading into what the ratio of ingredients is doing and why.

Until such a point where have agents not trained on human language or programming languages, I think something that’s also really good for people as well. - one obvious way to do things

- memory safe, thread safe, concurrency safe, cancellation safe

- locality of reasoning, no spooky action at a distance

- tests as a first class feature of the language

- a quality standard library / reliable dependency ecosystem

- can compile, type check, lint, run tests, in a single command

- can reliably mock everything, either with effects or something else, such that again we maintain locality of reasoning

The old saying that a complex system that works is made up of simple systems that work applies. A language where you can get the small systems working, tested, and then built upon. Because all of these things with towards minimising the context needed to iterate, and reducing the feedback loop of iteration.

I thought the same, but when Berkeley Mono got ligatures I gave them a go and never turned them off.

I think the truth is that any good monospace font is designed with an awareness of the grid those characters are laid out in. The rhythm and stability of that grid is a feature of monospace fonts. It lets us line up text, draw shapes and so on.

You would think not having the underlying characters visible would be an issue, but ligatures are just symbols like any other. In a short time you learn to read them, like you would any contracted word.

Spatial Audio for music is interesting and when properly mixed for it a song can be great. But I’m not going out of my way to find those songs.

When it absolutely excels is movies and TV, the immersion is spectacular.

I’m holding out for an example of Spatial Audio/Dolby Atmos where the immersion it can provide adds to the experience. Orchestral music is probably the best place to find that.

Would it make more sense to consider the response from the DB, like a response from any other system or user input, and take the parse don’t validate approach?

After all, the DB is another system, and its state can be different to what you expected.

At compile time we have a best guess. Unless there was a way to tell the DB what version of the schema we think it has, it could always be wrong.

The name should represent what the function does, it should indicate its purpose.

The distinction is useful even when it’s structurally identical to another function.

Two identical functions in different contexts or domains often diverge. When they’re not arbitrarily bound by the fact they have the same contents it’s easy to extend one and not the other.

During compilation, they could both end up using the same actual implementation (though I’m not sure if any compiler does this).

Test names are one of those things that are painful because it’s obvious to you as you write it, but there’s an extra hassle to switch gears in your head to describe what the contents of the test is doing.

It is really valuable when they are named well.

I’ve found this is where LLM can be quite useful, they’re pretty good at summarising.

Someday soon I think we’ll see a language server that checks if comments still match what they’re documenting. The same for tests being named accurately.

Perhaps others have made this connection before…it does feel like some sort of universal law of physics.

This has been on my mind too, and I can’t help but think the fundamental concept underpinning it all is causality.

Reading about “consistency as logical monotonicity” — CALM [0], after diving into Spanner, there’s definitely something to databases beyond CAP.

I’m yet to find a simple and clear law or theorem that captures what you’re hinting to, but it feels like we must be getting close. This has been bouncing around my head for a few years since I first wrote a toy CRDT DB.

It seems to show up anywhere we have more than one system with independent memory (a place where state can be held), needs to maintain a shared representation or fact about something.

Within one memory (akin to a reference frame in quantum physics), we can do anything we want. We can mutate state without any interaction or interference from the outside world. This also sounds like a pure function. By itself, this is academic, theoretical—it does not, it does not exist. If a tree falls in the woods.

So if we want to interact with any other systems, we then need to coordinate, and the question is how.

The issue and pattern seems to rhyme everywhere. CPUs, HDD, SSD, file systems, networks, UIs, people, teams, etc. The best possible collaboration seems to be that which requires the least coordination. Money is a good example here, someone can string together a series of products from companies who know nothing about each other, by coordinating with money - as a means of exchange. Not to mention being able to buy complex technology with no idea the supply chain behind it. I don’t have to coordinate with a mine to use a computer, which contains something from said mine.

It sort of looks like distributed databases build a virtual memory on top of many smaller memories, which need to share some parts with each other to protect the system as a whole.

New information may need a few writes before it can be considered a new fact. I think this is an implementation detail, in that it’s irrelevant to the observer (who has no control over it).

This isn’t eventual consistency, which is perhaps the cruder form of this where the implementation detail above is wide open for all to see. Instead new information is available immediately, just your definition of immediately is different to the databases.

It follows then, that as an observer of the system you cannot violate causality in any way by learning information from the future, while they are still in the past.

My understanding from Spanner is that when you ask for a read, you provide or are issued a timestamp which provides the causal information to determine what you are allowed to know.

The system can then maintain both a true and correct representation of what it knows, and an incomplete working memory of what it is trying to know next (the writes which need to be committed into multiple memories).

Memory being anything from ram, ssd, carrier pigeon, lines in sand, etc.

I think where this breaks most of our brains is that it’s a universe simulation.

And both time and causality are fundamental invariants of the stability of the system, but are leaky abstractions that we need to deal with.

In CALM this is abstracted into what is effectively entropy. If your program never moves backward in time/loses entropy it’s CALM (I think). In earlier works I think Lamport and vector clocks were used, in Spanner it’s based on very precise measurements of our own time, where the maximum speed of information (ie speed of light) is the greater of the smallest unit of time we can measure (the precision of the GPS clock) and the time it takes for new data to become available in the system.

The other part where this differs from the read world is that the speed of information, the latency of a request, is different for reads and writes. Not true in the quantum world where an everything is a wrote (I think). Then, consider that in our own reference frame we can do a huge amount of work while waiting for a db read/write, something that would violate the speed of light if not in our virtualised world.

We cannot break causality in the world we live and breathe in, but we do it all the time in our simulated ones.

[0] https://arxiv.org/abs/1901.01930

It’s a consequence of thinking light/dark modes are personal preference when they’re accessibility features.

And when viewed from that lens you can see how off-putting it is when it’s a paid feature, or only adjustable when logged in, or a user-setting that seemingly always defaults to light.

One practice I really like with Feature Flags is having a flag budget. Building on the idea of an error budget, where some percentage of errors is okay, but at a certain threshold feature work needs to slow or stop to get it under control.

Having a limit of in-flight feature flags, means you’re incentivised to clean them up when they’re done (or decide they’re long running features etc), and it can help keep a handle on in progress work.

But mostly, feature flags are a powerful tool—they can hurt as much as they help if you don’t use them right.

There’s 2 mains ways to do feature flags.

Coupled to a code deploy or not.

Changing flags on deploy is simple, great when you have a fast pipeline and only a few flags.

But at some point it becomes useful to decouple feature release from code deploy.

And the only way to do that, is to be able to change the value of a flag out-of-band of a pipeline.

Then you have the capability to test new code in environments before prod and in small parts of prod—canary releases and so on.

Configuration and settings management overlaps with feature flags, but note that often the value comes from the ability to test and safely deploy new code into production environments (more of a release flag), that to enable a feature for a specific user. It just so happens that the use cases and technical implementations overlap so frequently it’s sometimes less work to use the same system.

As someone with misophonia[0] this would be an unbearable and unsettling punishment. Hearing other people eating is like being stuck in an elevator after lunch, when someone drops their guts. But, even quiet chatter or tv is enough to mask the sound.

To say nothing of the boredom of 15 minutes of a silence.

[0] https://en.m.wikipedia.org/wiki/Misophonia > Misophonia (or selective sound sensitivity syndrome, sound-rage) is a disorder of decreased tolerance to specific sounds or their associated stimuli, or cues. These cues, known as "triggers", are experienced as unpleasant or distressing and tend to evoke strong negative emotional, physiological, and behavioral responses that are not seen in most other people.

Observability as a shared concept has followed Agile and DevOps.

Something with a real meaning that is enables a step-change is development practices. Adoption is organic initially because the pain it solves is very real.

But as awareness of the idea grows it threatens established institutions and vendors, who must co-opt the concept and redefine it such that they are included.

If they can’t be explicitly included (logs, metrics, traces)[0], then they at least make sure the definition is becomes so vague and confused that they are not explicitly excluded[1].

Wide events and a good means to query them covers everything, but not if you as a vendor cannot store and query wide events.

[0] as the article notes, one of these is not like the other. [1] Is Scrum Agile? What do you mean a standup can’t go for an hour? See also DevOps as a role.

This is because you define what you want the components result to be. But not how to do it.

The return of a component render is a description of more work to do. When that’s done is up to reacts scheduler.

Once react has rendered a tree it then commits it to the dom.

Manipulating dom elements at runtime is imperative work. Doing it server side you get the same declarative capability as react, but no runtime behaviour.

React elements not being 1:1 with the DOM is what enables us to also target native, tuis, and so on.

It’s all about feedback loops, and how fast you can make them.

I was thinking about this recently and came to the same conclusion as the tweet. I almost never use debuggers.

Print debugging outputs to a single flat buffer, stdout (or stderr, but the point remains). Where you place those statements matters, because you should be able to predict the output to stdout. If you can’t, you get to update your mental model.

The order matters, the timing matters, the brutal simplicity matters.

Full on debuggers are helpful tools to explore someone else’s program, but mostly I find their only advocates come from those whose feedback loop with print statements is measured in minutes to hours, not seconds.

Of course if it takes 30 minutes to rebuild your app, or 5 minutes to setup the game state your working on, pausing to inspect the state of the world is going to be easier than print statements.

But if it only takes seconds to rebuild, nothing will beat print statements.

The comments here from the first hour are depressingly predictable. Just because you don’t take issue with something doesn’t mean someone else won’t. Brands have always been bigger than their main product. People wear branded tshirts, put stickers on their laptops to show some kind of affinity with a brand.

The opposite can also be true. For reasons they don’t want to use the product anymore, and they want it to be known (they mention they’ve been using it for over a decade).

The reasons themselves have been debated here on the DHH posts themselves, so how about we ask the bigger question.

How can you join the political debate publicly, without risking customers? Or should you risk loosing customers?

How do we talk politics at work? Because sometimes it’s needed. I don’t have the answers, suffice to say starting from a position of respect and a high-trust environment is important.

A former colleague once gave me some of the most important advice I’ve ever received a few years ago:

Spend your money on what goes between you and the ground.

For about a decade I sat on a version of an IKEA chair, with a mesh neck and foam cushion. They’re good for the price, but I wish I upgraded sooner. The foam only ever lasted about 2 years.

The Aeron is a nice chair, but use one first before you buy. They’re often available second hand for cheap, which is great value. For me, I found them to be a bit uncomfortable.

My main chair now is a Herman Miller Cosm, with the high back. I love it, it looks great, and the leaf arms are the first chair arms I’ve ever left on a chair. The main draw for me was the single mesh across the whole chair. I find mesh more comfortable, and it doesn’t wear like foam.

I also have a qor360 active sitting chair. These take getting used to, but they’re pretty good. Definitely keeps me alert.

Finally I got one of the cheaper folding desk treadmills (walking pad I think?). I found I can walk for hours if my brain has something interesting to do.

Overall I spend most of the time in the Cosm. I’ve spent more on fixing back pain than any of these, and each has meaningfully improved that.

As our bodies all vary in shape, size, weight and proportions, you need to try as many different options to find what features are best for you.

The Cosm and qor360 ain’t cheap, but they’ve been worth it for me.

There’s an inherent amount of complexity with two (or more) systems talking to each other over a network. You can shift the balance from one to the other, but it’s always there.

GraphQL pushes complexity into the backend where it’s arguably easier to manage, in return the frontend can have more flexibility and autonomy as a client.

REST pushes the complexity to the frontend requiring clients to orchestrate multiple calls, and reconcile the data models in the API to their own.

GraphQL got a lot of early praise for having good tooling that can provided end-to-end type safety, autogenerated client and server frameworks, and so on. OpenAPI has more or less caught up on this front now.

You can’t just take a REST api and convert it into a GraphQL api. Like going from dynamic to static types, with GraphQL it pays off to define your data model and reuse it throughout the API.

I’ve seen simple things like using the same field name, or same set of fields for a given type get bikeshedded for months. You can get with this for REST APIs, but not GraphQL.

When I first used copilot a few months after it came out, I thought it was wildly undervalued—I’d pay $100/m for the value and time saved.

It’s become such an integral part of my workflow, that until Zed got Copilot integration I couldn’t switch to it.

Of course I’d prefer to continue paying $10 a month.

Interestingly I’ve had Phillips Hue for well over 5 years now, but no smart switches. One of the bigger QoL improvements I’ve ever made, especially when the lights automatically change throughout the day.

Between a few specifically named scenes in the Home app, Siri on HomePods, and the normal switches it’s been quite reliable.

Worst case if the internet is down (which stops Siri’s voice to text), or if there’s a wifi issue, or if I’m right next to a switch: I can turn the switch off, wait 2 seconds, turn on and the hue light turns on to a neutral color at a reasonable brightness.

I think any combination of smart lights or switches or both is manageable as long as you have a fast, simple means to power cycle the lights (and they default-on with power).

I don’t think dumb light switches should disappear. They’re phenomenally reliable, have a predictable behaviour, are easily discoverable and never move. The last one is important because when you urgently need to turn a light on, nothing beats a dumb switch.

But YAML _looks_ like it’s easy to write by hand.

It looks easier than JSON to write by hand. JSON at least makes it known that you’ll have a better time generating it, that writing it.

It’s like those doors that open by pushing them. Some open both ways, some have a handle to grab and open it to you, some have a plate for you to push on.

And some (like YAML) have a plate on the side you need to pull, and a handle on the side you need to push.

My general understanding is that a fiber is a lightweight thread, and conceptually can be implemented inside any program that wants to schedule its own fibers.

They’re distinct from CPU threads.

When I last dived into fibers I discovered windows was trying to make them a thing quite a while ago which is why we have things like [0].

Fibers being virtual threads, are recursive. In the same way you can run a VM inside a VM, you could build fibers on top of fibers (which sounds like a recipe for unpredictable performance).

[0] https://learn.microsoft.com/en-us/windows/win32/procthread/u...

In a way, it seems like async Rust appears more often when you need to do io operations, and not so much when you just need to do work in parallel.

Yep this makes sense to me.

If your workload is CPU-bound then context switching to make progress on 10 tasks concurrently, is going to be slower than doing them sequentially.

But if it’s IO-bound you will spend most of your time waiting, which you could use to make progress on the other tasks.

At a granular level, I don’t think it’s meaningfully possible. Think about it, how often do you do the same thing twice?

The old programmer joke about writing a script instead of doing the same thing twice, hides a truth that the value of our work is doing stuff we haven’t done before.

But, if you have never done a task before how do you measure it? And if you have done a task before, why are you doing it again, can’t you reuse what you did last time?

There’s factors out of most devs control that influence their productivity—a mark of a senior dev (IMHO) is someone who understands this and actively unblocks themselves and others.

So no, I don’t think you can directly measure a devs productivity.

Measuring a team’s productivity is equally challenging, as the team is affected by things out of their control too, and the team is also doing work that hasn’t been done before.

Instead, I think looking towards the health and quality metrics of the team’s output (say a service they created/maintain), the health of the team, and actually speaking to engineers is the best chance we have of understanding the how well the team is working.

Sure not everyone who bought the game or a ship at this stage has enjoyed it, but a lot of us have. Even in its unfinished state there’s a lot of fun to be had.

And, part of the game at this stage is watching it develop.

The amount raised/sold so far is massive, but people keep buying because they’re getting something out of it.

Releasing code to prod is the end of the feedback loop, until then you have possibly working software. The long you wait until release, the less likely it will work as you expect.

You don’t need to release to users, that can happen later.

Every team I’ve been on that could release daily never wanted to go back to anything less.

The tooling is still too hard for mass adoption by teams, but the benefits are real.