More from tonsky.me
Каким должен быть редактор кода в 2024 году? Почему Vim морально устарел, а IDEA, кажется, сдает позиции? Популярность Zed, минимализм SublimeText, гибкость Emacs и многое другое в новом выпуске.
Squint is a light-weight dialect of ClojureScript with a compiler and standard library. “The idea is that when you squint, it still looks like CLJS”.
There’s a wonderful article by Sebastian Bensusan: “We need visual programming. No, not like that.” (the dot is part of the title ¯\_(ツ)_/¯). In it, Sebastian argues that we shouldn’t try to replace all code with visual programming but instead only add graphics where it makes sense: Most visual programming environments fail to get any usage. Why? They try to replace code syntax and business logic but developers never try to visualize that. Instead, developers visualize state transitions, memory layouts, or network requests. In my opinion, those working on visual programming would be more likely to succeed if they started with aspects of software that developers already visualize. I love diagrams myself! Whenever I encounter a complicated task and try to solve it in code, it always gets messy. But after drawing a diagram, my understanding improves, and the code gets cleaner. Win-win! Here’s one I made for button states in Humble UI: I bet you thought buttons are easy? Me too, at first. But after certain threshold your head just can’t fit all the states and transitions. Or for an image upload component: Again: it would’ve been easy if not for error handling. But with a principled approach, you can get through any of that. Sebastian gives many more examples of useful visualizations in his article, too. But now, how does all this relate to code? I think there’re four levels. Level 0: Diagrams live separately You draw them in a separate tool, then use that to help you write code. Maybe put them on a wiki for other people to see. The point is: the diagram lives completely separate from the code. Downsides: hard to discover, can get out of date. This is what I did in the two examples above, and I guess what most of us can do given modern tools. But hey—it’s still not that bad! Level 1: Diagrams live next to code One simple trick would solve the problem of discovery: what if we could put images into our text files? Currently, the best you can do is this: +-----+ --> | N_4 |------ <--- +-----+ +-----+ | |-----| R_3 | | 15 | | 5 +-----+ |50 | | | +-----+ ---> | +-----+ | 70 | N_2 |------ | | N_3 | | +-----+ | | +-----+ | | 15 | | | 30 | | 10 | +-----+ <--- | | @ | ----| S |--------| | @ | <@@@ +-----+ | V | | | | | 10 | | | +-----+ | V | | R_2 | +-----+ | +-----+ | E | | | | +-----+ | | | 40 | | | V | 10 | | | | +-----+ | V | -----| R_1 |-----| | +-----+ | | ---> +-----+ | |------------------| D |--------- 10 +-----+ But it gets messy real quick. What if we could do this instead? Upsides: easy to implement (once everybody agrees on how to do that), universal (probably many other use cases). Downsides: still can get out of date. “Comments are not code”—the same applies here. Oh, and if you are coding in a terminal, this party is not for you. Sorry. We are thinking about the future here. Level 2: Diagrams are generated from code This is what Sebastian was hinting at. Code and diagrams co-exist, one is generated from the other. Generating diagrams from code is definitely something IDEs can do: Upsides: Always up to date. Non-invasive: can be integrated into IDE without affecting how code is stored. Downsides: It can help you understand, but can it help you think? Probably not very visually appealing, as these things tend to be. It’s hard to automatically lay out a good diagram. Level 3: Diagrams are code This is what the endgame should be IMO. Some things are better represented as text. Some are best understood visually. We should mix and match what works best on a case-by-case basis. Don’t try to visualize simple code. Don’t try to write code where a diagram is better. One of the attempts was Luna. They tried dual representation: everything is code and diagram at the same time, and you can switch between the two: From luna-lang.org But this way, you are not only getting benefits of both ways, you are also constrained by both text and visual media at the same time. You can’t do stuff that’s hard to visualize (loops, recursions, abstractions) AND you can’t do stuff that’s hard to code. No, I think textual coding should stay textual where it works, BUT we should also be able to jump into a diagram tool, draw a state machine there and execute it the same way we execute text code. And when I mean draw, I mean draw. With direct manipulation, all that jazz. And without converting it back to text. So what I’m saying is: diagrams should not replace or “augment” text. They should be just another tool that lives next to the text. But a tool on its own. Think of it as a game engine like Godot or Unity. In them, you can write normal text code, but you can also create and edit scenes. These scenes are stored in their own files, have specialized editors that know how to edit them, and have no code representation. Because why? The visual way in this particular case is better. So the challenge here is not about integrating diagrams, but to think about which types of diagrams can be useful, can work better than code, and be directly executed. Non-goal: Diagrams replace code Important note: we are not talking about doing code graphically. This is just a less convenient way of doing things that text already does. We are also not talking about no-code platforms: sometimes code is just better. But until this bright future arrives, put a diagram or two on the wiki. Your teammates will thank you for that.
So I was at the Local-First Conf the other day, listening to Martin Kleppmann, and this slide caught my attention: Specifically, this part: But first, some context. What is local-first? For the long version, go to Ink & Switch, who coined the term. Or listen for Peter van Hardenberg explaining it on LocalFirst.fm. Here’s my short version: It’s software. That prefers keeping your data local. But it still goes to the internet occasionally to sync with other users, fetch data, back up, etc. If it doesn’t go to the internet at all, it’s just local software. If it doesn’t work offline with data it already has, then it’s just normal cloud software. You all know the type — sorry, Dave, I can’t play the song I just downloaded because your internet disappeared for one second... But somewhere in the middle — local-first. We love it because it’s good for the end user, you and me, not for the corporations that produce it. What’s the problem with local-first? The goal of local-first software is to get control back into the hands of the user, right? You own the data (literally, it’s on your device), yada-yada-yada. That part works great. However, local-first software still has this online component. For example, personal local-first software still needs to sync between your own devices. And syncing doesn’t work without a server... So here we have a problem: somebody writes local-first software. Everybody who bought it can use it until the heat death of the universe. They own it. But if the company goes out of business, syncing will stop working. And companies go out of business all the time. What do we do? Cue Dropbox The solution is to use something widely available that will probably outlive our company. We need something popular, accessible to everyone, has multiple implementations, and can serve as a sync server. And what’s the most common end-user application of cloud sync? Dropbox! Well, not necessarily Dropbox, but any cloud-based file-syncing solution. iCloud Drive, OneDrive, Google Drive, Syncthing, etc. It’s perfect — many people already have it. There are multiple implementations, so if Microsoft or Apple go out of business, people can always switch to alternatives. File syncing is a commodity. But file syncing is a “dumb” protocol. You can’t “hook” into sync events, or update notifications, or conflict resolution. There isn’t much API; you just save files and they get synced. In case of conflict, best case, you get two files. Worst — you get only one :) This simplicity has an upside and a downside. The upside is: if you can work with that, it would work everywhere. That’s the interoperability part from Martin’s talk. The downside is: you can’t do much with it, and it probably won’t be optimal. But will it be enough? Version 1: Super-naive Let’s just save our state in a file and let Dropbox sync it (in my case, I’m using Syncthing, but it’s the same idea. From now on, I’ll use “Dropbox” as a common noun). Simple: But what happens if you change the state on two machines? Well, you get a conflict file: Normally, it would’ve been a problem. But it’s not if you are using CRDT! CRDT is a collection of data types that all share a very nice property: they can always be merged. It’s not always the perfect merge, and not everything can be made into a CRDT, but IF you can put your data into a CRDT, you can be sure: all merges will go without conflicts. With CRDT, we can solve conflicts by opening both files, merging states, and saving back to state.xml. Simple! Even in this form, Dropbox as a common sync layer works! There are some downsides, though: conflicting file names are different between providers, some providers might not handle conflicts at all, it needs state-based CRDT. Version 2: A file per client The only way to avoid conflicts is to always edit locally. So let’s give each client its own file! Now we just watch when files from other clients get changed and merge them with our own. And because each file is only edited on one machine, Dropbox will not report any conflicts. Any conflicts inside the data will be resolved by us via CRDT magic. Version 3: Operations-based What if your CRDT is operation-based? Meaning, it’s easier to send operations around, not the whole state? You can always write operations into a separate append-only file. Again, each client only writes to its own, so no conflicts on the Dropbox level: Now, the operations log can grow quite long, and we can’t count on Dropbox to reliably and efficiently sync only parts of the file that were updated. In that case, we split operations into chunks. Less work for Dropbox to sync and less for us to catch up: You can, of course, save the position in the file to only apply operations you haven’t seen. Basic stuff. Theoretically, you should be able to do operational transformations this way, too. Demo A very simple proof-of-concept demo is at github.com/tonsky/crdt-filesync. Here’s a video of it in action: Under the hood, it uses Automerge for merging text edits. So it’s a proper CRDT, not just two files merging text diffs. Conclusion If you set out to build a local-first application that users have complete control and ownership over, you need something to solve data sync. Dropbox and other file-sync services, while very basic, offer enough to implement it in a simple but working way. Sure, it won’t be as real-time as a custom solution, but it’s still better for casual syncs. Think Apple Photos: only your own photos, not real-time, but you know they will be everywhere by the end of the day. And that’s good enough! Imagine if Obsidian Sync was just “put your files in the folder” and it would give you conflict-free sync? For free? Forever? Just bring your own cloud? I’d say it sounds pretty good.
More in programming
Discover how The Epic Programming Principles can transform your web development decision-making, boost your career, and help you build better software.
Denmark has been reaping lots of delayed accolades from its relatively strict immigration policy lately. The Swedes and the Germans in particular are now eager to take inspiration from The Danish Model, given their predicaments. The very same countries that until recently condemned the lack of open-arms/open-border policies they would champion as Moral Superpowers. But even in Denmark, thirty years after the public opposition to mass immigration started getting real political representation, the consequences of culturally-incompatible descendants from MENAPT continue to stress the high-trust societal model. Here are just three major cases that's been covered in the Danish media in 2025 alone: Danish public schools are increasingly struggling with violence and threats against students and teachers, primarily from descendants of MENAPT immigrants. In schools with 30% or more immigrants, violence is twice as prevalent. This is causing a flight to private schools from parents who can afford it (including some Syrians!). Some teachers are quitting the profession as a result, saying "the Quran run the class room". Danish women are increasingly feeling unsafe in the nightlife. The mayor of the country's third largest city, Odense, says he knows why: "It's groups of young men with an immigrant background that's causing it. We might as well be honest about that." But unfortunately, the only suggestion he had to deal with the problem was that "when [the women] meet these groups... they should take a big detour around them". A soccer club from the infamous ghetto area of Vollsmose got national attention because every other team in their league refused to play them. Due to the team's long history of violent assaults and death threats against opposing teams and referees. Bizarrely leading to the situation were the team got to the top of its division because they'd "win" every forfeited match. Problems of this sort have existed in Denmark for well over thirty years. So in a way, none of this should be surprising. But it actually is. Because it shows that long-term assimilation just isn't happening at a scale to tackle these problems. In fact, data shows the opposite: Descendants of MENAPT immigrants are more likely to be violent and troublesome than their parents. That's an explosive point because it blows up the thesis that time will solve these problems. Showing instead that it actually just makes it worse. And then what? This is particularly pertinent in the analysis of Sweden. After the "far right" party of the Swedish Democrats got into government, the new immigrant arrivals have plummeted. But unfortunately, the net share of immigrants is still increasing, in part because of family reunifications, and thus the problems continue. Meaning even if European countries "close the borders", they're still condemned to deal with the damning effects of maladjusted MENAPT immigrant descendants for decades to come. If the intervention stops there. There are no easy answers here. Obviously, if you're in a hole, you should stop digging. And Sweden has done just that. But just because you aren't compounding the problem doesn't mean you've found a way out. Denmark proves to be both a positive example of minimizing the digging while also a cautionary tale that the hole is still there.
One rabbit hole I can never resist going down is finding the original creator of a piece of art. This sounds simple, but it’s often quite difficult. The Internet is a maze of social media accounts that only exist to repost other people’s art, usually with minimal or non-existent attribution. A popular image spawns a thousand copies, each a little further from the original. Signatures get cropped, creators’ names vanish, and we’re left with meaningless phrases like “no copyright intended”, as if that magically absolves someone of artistic theft. Why do I do this? I’ve always been a bit obsessive, a bit completionist. I’ve worked in cultural heritage for eight years, which has made me more aware of copyright and more curious about provenance. And it’s satisfying to know I’ve found the original source, that I can’t dig any further. This takes time. It’s digital detective work, using tools like Google Lens and TinEye, and it’s not always easy or possible. Sometimes the original pops straight to the top, but other times it takes a lot of digging to find the source of an image. So many of us have become accustomed to art as an endless, anonymous stream of “content”. A beautiful image appears in our feed, we give it a quick heart, and scroll on, with no thought for the human who sweated blood and tears to create it. That original artist feels distant, disconected. Whatever benefit they might get from the “exposure” of your work going viral, they don’t get any if their name has been removed first. I came across two examples recently that remind me it’s not just artists who miss out – it’s everyone who enjoys art. I saw a photo of some traffic lights on Tumblr. I love their misty, nighttime aesthetic, the way the bright colours of the lights cut through the fog, the totality of the surrounding darkness. But there was no name – somebody had just uploaded the image to their Tumblr page, it was reblogged a bunch of times, and then it appeared on my dashboard. Who took it? I used Google Lens to find the original photographer: Lucas Zimmerman. Then I discovered it was part of a series. And there was a sequel. I found interviews. Context. Related work. I found all this cool stuff, but only because I knew Lucas’s name. Traffic Lights, by Lucas Zimmerman. Published on Behance.net under a CC BY‑NC 4.0 license, and reposted here in accordance with that license. The second example was a silent video of somebody making tiny chess pieces, just captioned “wow”. It was clearly an edit of another video, with fast-paced cuts to make it accommodate a short attention span – and again with no attribution. This was a little harder to find – I had to search several frames in Google Lens before I found a summary on a Russian website, which had a link to a YouTube video by metalworker and woodworker Левша (Levsha). This video is four times longer than the cut-up version I found, in higher resolution, and with commentary from the original creator. I don’t speak Russian, but YouTube has auto-translated subtitles. Now I know how this amazing set was made, and I have a much better understanding of the materials and techniques involved. (This includes the delightful name Wenge wood, which I’d never heard before.) https://youtube.com/watch?v=QoKdDK3y-mQ A piece of art is more than just a single image or video. It’s a process, a human story. When art is detached from its context and creator, we lose something fundamental. Creators lose the chance to benefit from their work, and we lose the opportunity to engage with it in a deeper way. We can’t learn how it was made, find their other work, or discover how to make similar art for ourselves. The Internet has done many wonderful things for art, but it’s also a machine for endless copyright infringement. It’s not just about generative AI and content scraping – those are serious issues, but this problem existed long before any of us had heard of ChatGPT. It’s a thousand tiny paper cuts. How many of us have used an image from the Internet because it showed up in a search, without a second thought for its creator? When Google Images says “images may be subject to copyright”, how many of us have really thought about what that means? Next time you want to use an image from the web, look to see if it’s shared under a license that allows reuse, and make sure you include the appropriate attribution – and if not, look for a different image. Finding the original creator is hard, sometimes impossible. The Internet is full of shadows: copies of things that went offline years ago. But when I succeed, it feels worth the effort – both for the original artist and myself. When I read a book or watch a TV show, the credits guide me to the artists, and I can appreciate both them and the rest of their work. I wish the Internet was more like that. I wish the platforms we rely on put more emphasis on credit and attribution, and the people behind art. The next time an image catches your eye, take a moment. Who made this? What does it mean? What’s their story? [If the formatting of this post looks odd in your feed reader, visit the original article]
When the iPhone first appeared in 2007, Microsoft was sitting pretty with their mobile strategy. They'd been early to the market with Windows CE, they were fast-following the iPod with their Zune. They also had the dominant operating system, the dominant office package, and control of the enterprise. The future on mobile must have looked so bright! But of course now, we know it wasn't. Steve Ballmer infamously dismissed the iPhone with a chuckle, as he believed all of Microsoft's past glory would guarantee them mobile victory. He wasn't worried at all. He clearly should have been! After reliving that Ballmer moment, it's uncanny to watch this CNBC interview from one year ago with Johny Srouji and John Ternus from Apple on their AI strategy. Ternus even repeats the chuckle!! Exuding the same delusional confidence that lost Ballmer's Microsoft any serious part in the mobile game. But somehow, Apple's problems with AI seem even more dire. Because there's apparently no one steering the ship. Apple has been promising customers a bag of vaporware since last fall, and they're nowhere close to being able to deliver on the shiny concept demos. The ones that were going to make Apple Intelligence worthy of its name, and not just terrible image generation that is years behind the state of the art. Nobody at Apple seems able or courageous enough to face the music: Apple Intelligence sucks. Siri sucks. None of the vaporware is anywhere close to happening. Yet as late as last week, you have Cook promoting the new MacBook Air with "Apple Intelligence". Yikes. This is partly down to the org chart. John Giannandrea is Apple's VP of ML/AI, and he reports directly to Tim Cook. He's been in the seat since 2018. But Cook evidently does not have the product savvy to be able to tell bullshit from benefit, so he keeps giving Giannandrea more rope. Now the fella has hung Apple's reputation on vaporware, promised all iPhone 16 customers something magical that just won't happen, and even spec-bumped all their devices with more RAM for nothing but diminished margins. Ouch. This is what regression to the mean looks like. This is what fiefdom management looks like. This is what having a company run by a logistics guy looks like. Apple needs a leadership reboot, stat. That asterisk is a stain.