Linus's stream

Podcast conversation about self made tools, side projects, and the relationships we have with our digital tools, with the Muse team - https://museapp.com/podcast/42-self-made-tools/

While watching Richard Feldman's talk on the Roc programming language, I came across an interesting bit of syntax sugar around chaining effects he calls "backpassing". Backpassing is sugar around passing a "callback" type function to another function. In Oak syntax, it lets us rewrite a program like

with fs.readFile('/etc/hosts') fn(file) {
	std.println(file)
}

... into this

// Roc uses `<-`, but since `<-` means something
// in Oak, we use `:= await` here instead, where
// "await" is a new language keyword.
file := await fs.readFile('/etc/hosts')
std.println(file)

It's pure sugar -- it desugars at the parser stage, which is clever and different than other implementations of await that depend on some Promise or Future type in the core language. Its advantage is that it relieves the indentation hell of passing callbacks at a pure syntax level. Its disadvantage is that there's no visible function literal in the program anymore, and I imagine compile errors it generates won't be pretty. This is a bit of invisible syntax magic, which is generally against my (and hence Oak's) philosophy of simplicity promoting understandability.

Nonetheless, I'll be mulling it over and lightly considering it for Oak.

As an extension to my note on media-native programming languages, I wanted to note: Oak's standard library contains built-in support for not only parsing and rendering JSON, but also Markdown content via the json and md libraries. Markdown support is built into the language tooling, and it means when I write Oak programs, I feel like Oak "speaks Markdown" natively as an information format. It means there's no additional development effort for Oak programs to really support Markdown -- supporting it is about as easy as supporting plain text, at least in rendering, processing, and parsing. As a result, most tools I'm building with Oak where Markdown support makes sense, like this stream site, support Markdown.

I think this demonstrates one successful case study of a language that built in support for unconventional data formats, and benefited in the kinds of software and tools built from it.

QR codes for software distribution

I love how lightweight and error-resistant QR codes are. You can stick them anywhere and display them nearly everywhere. What if we could pack small pieces of contextually relevant software, like to-do apps, chat clients, little maps, and restaurant reservation systems into big QR codes to distribute software? Instead of giving you a download link, I just give you a QR code to scan, and your phone loads the program directly from the code and starts executing.

I want to imagine a future where software isn't necessarily "installed" and "uninstalled", but ephemeral like web apps and situationally delivered to our devices. Little micro pieces of functionality, quickly loaded and unloaded, scooped up by our devices from little tags we can touch in the physical world.

It would be so cool to be able to share a game I made with a friend, by giving them a QR code they can scan whenever they want to play it. No install, no download. Just load it up from the code!

Everything new is also old -- this reminds me of loading up software from floppy disks! But I think it can be different. QR codes can be so much more versatile and ubiquitous, so much more error-resistant, infinitely cheaper, and if delivered on screens, forever changeable. It's also decentralized, in a way. If I give you a QR code, you don't need to rely on some app store being online to use that new program.

One way I can imagine this working is some lightweight "base" app or virtual machine installed on your phone, that can load and execute very compact bytecode from a QR code.

Media-native programming languages

Modern programming languages are very good at handling strings. Not only do they have built-in representations of strings in the common string "type", they have built-in support (into the language or as standard libraries) for searching within strings, comparing them, slicing them, combining them, and various other useful operations. As a result, most software we use today all expect us to enter text data. They speak the language of "text".

By contrast, modern tools handle images and audio only reluctantly. Images and audio are the native I/O types of the human mind, if you will -- it's much higher-bandwidth, and much more closer to "the organs" even if they're farther from "the metal" of the computer.

What if we could build into programming languages the same capabilities for working with rich media, as we've done for strings? What if OCR and speech to text, seeking and searching for objects or strings within video, photos, and audio, were all as easy as photo.findAll(:car) or audio.transcribe({ lang: 'en_us' }), built into your compiler? I think it would usher in a whole new age of software tools that let us interact with them in richer, more organic ways. If reading text from an image was as easy as reading text out of a binary buffer, how many more tools would let us take pictures to capture information?

You might say, "this sounds like a huge amount of complexity, Linus! No sane PL would ever do this!" But we've done this for text, because the tradeoffs are worth it -- Go ships out of the box with rich support for full UTF-8 text. This wasn't always the case. C, for example, has no native string type -- C works with bytes and characters, in the same way that current programming languages work with pixels and audio file buffers.

I submit to you: it doesn't have to be this way! We can create a world where we can program with rich visual and sonic information with the same ease with which we work with text. That day can't come quick enough.

Software design today usually lacks the kind of detail that's pervasive in reality. A part of me thinks what people really liked about 2000-2010 skeuomorphism in software design isn't actually skeuomorphism per se, but the richness of detail. Perhaps we should be less minimal in design?

From The Ongoing Computer Revolution, Butler Lampson (emphasis mine):

Xerox asked us to invent the electronic office, even though no one knew what that meant. We did, and everyone’s using it today. That makes it hard to remember what the world was like in 1972. Most people thought it was crazy to devote a whole computer to the needs of one person—after all, machines are fast and people are slow. But that’s true only if the person has to play on the machine’s terms. If the machine has to make things comfortable for the person, it’s the other way around. No machine, even today, can yet keep up with a person’s speech and vision.

I've been thinking about how we might integrate better machine intelligence into our thinking-writing tools, and one thesis I'm developing is that it's important that machines and humans can collaborate on the same document. Writing is how we think. If we want to think together with computers rather than using computers, we need to write together, not simply with the computer as a blunt tool for recording our own words.

Popular approaches often stick software-driven suggestions or connections in a sidebar or a context menu or squiggly red lines under our own text. But I really want my software to write alongside me, underneath my bullet points and in my margins, as if I'm editing and thinking together with a colleague. I want my eyes to slide seamlessly between my words and the machine's, and trust its voice.

I may sound pedantic, but I think there's a huge qualitative difference between a machine as a thought partner correcting my writing and being asked for help, versus the machine working with me and contributing proactively at a level equal to my own creative power.

I want to focus some slice of my research time on the question: how can we make collaborative authoring and thinking with computers more seamless?.

While implementing the Levenshtein edit distance algorithm in Oak, I came up with a handy quick benchmark helper function:

fn bench(name, f) {
	start := time()
	f()
	elapsed := time() - start
	'[bench] {{0}}: {{1}}ms' |> fmt.printf(
		name
		math.round(elapsed * 1000, 3) |> string()
	)
}

Use it as with bench('...') fn { ... }, like

[3, 4, 5, 6, 7] |>
	std.map(fn(n) int(pow(10, n))) |>
	with std.each() fn(max) {
		with bench(string(max)) fn {
			std.range(max)
		}
	}

for output like

[bench] 1000: 1.737ms
[bench] 10000: 13.052ms
[bench] 100000: 131.559ms
[bench] 1000000: 1316.693ms
[bench] 10000000: 12747.569ms

Naming Oak's std.uniq

I was thinking about adding a couple of functions for de-duplicating lists of values to Oak's standard library. Here, I ran into a naming problem. Names in the language standard library are really important! They have to be short and memorable, but accurately represent what they do with minimal room for confusion.

This was my plan: One function would take [a, b, b, a] and return [a, b, a]; and the other function will return [a, b]. In other words, one returns a list that de-duplicates consecutive occurrences of a thing into just one, and the other sorts before de-duplicating so that elements occur at most once in the whole list.

It seems like different languages and environments use the name uniq to mean either of these operations. Some languages also use dedup for the other. What should Oak do?

For now, I think I'll just implement the first of the two functions, and call it uniq to be memorable. The other is a simple list |> sort() |> uniq() if it's needed, and having one name and one function reduces room for confusion. It also mirrors the UNIX command line idiom sort | uniq nicely, which feels right. This approach also means std.uniq won't need to depend on sort.sort, which depends on std; so this avoids a circular dependency.