What developer products can learn from language design

matrixpartners_gray

Programming languages and developer products have similar adoption drivers

I learned to program in Java but rarely encountered Java in production codebases. I haven't written Java in years, and neither have the vast majority of young programmers as far as I can tell.[1] I've recently been reflecting on what makes programming languages come and go.

A handful of extrinsic and intrinsic factors in language design make a language popular. These lessons are interesting to examine because they apply more broadly to all developer products.

Power laws govern popularity

Popularity for programming languages follows a power-law distribution. Only a handful of languages reach mainstream adoption. All other languages end up with niche use cases. For example, if you looked at the top 50 languages by pull request on Github in any given quarter, you would notice that the top 10 most popular languages account for over 80% of pull request activity.

Q4 2021 Github Language popularity by pull request

Let's dive into some of the factors behind this.

Factors that affect adoption

Platform shifts

Platform shifts are the primary driver behind the adoption of a new programming language. As new platforms emerge, developers embrace the supported language(s). The rise of web browsers and Javascript is an excellent example of this dynamic.[2]

Netscape Navigator launched in the late 90s and became one of the most widely used web browsers. It shipped with Javascript support for creating rich dynamic interactions and was a significant improvement on earlier browsers that were just hyperlinked applications.

Netscape market share

So, if you were a developer at the time and wanted to reach a large user base with an application that manipulated the DOM in any interesting way, you didn't have much of a choice. You had to adopt Javascript. As Microsoft's Internet Explorer (IE) browser competed with Netscape, it too supported Javascript. IE’s embrace of Javascript ensured that it would be a cross-platform scripting language and outlive the death of Netscape.

For a new language to become popular, it has to ride the adoption of an emerging platform. Today, Javascript is widely popular because web browsers are the predominant user interfaces.

Simplicity: worse is better

Languages that start small and grow over time tend to outlast languages that attempt to do too much from the start. This is because small languages have a lower barrier to entry. "Small language" is a term I am borrowing from Guy Steele's timeless talk on growing a language. A small language is one where there are fewer concepts to learn. The base primitives are simple and intuitive.

I often think of developers learning a new language as having a specific amount of "RAM" in their mind to devote to the fundamental concepts. The fewer concepts there are to remember, the less you check the docs or StackOverflow for syntax or semantic help.

Low “RAM” simplicity is difficult to achieve. It often looks like doing less instead of more.

Richard Gabriel emphasized this notion of doing less when designing a new language in an essay titled "Worse is Better." He argues that simplicity should take precedence over other language features such as correctness or completeness. Simplicity, he claims, is why C spread much faster than Lisp. C was easier to pick up because it had fewer concepts to master relative to other languages at the time. Simplicity is simply — pun intended — a better bet for virality.

That said, if a language is too small to be useful, it will not get very far. This brings me to the final driver of language adoption.

Composability: creating new vocabulary

Simplicity is necessary for adoption, but it is not sufficient for a mainstream language that endures. Programming languages need to evolve with users' needs, and composability is the key to that evolution.

Composability makes it easy to define new language primitives and abstractions. This is the holy grail in language design because it empowers users to mold the language and expand its capabilities. Creating new abstractions is how languages grow their TAM and reach more developers.

We can think about language growth along two axes 1) new feature direction: open-sourced versus centralized 2) how easy it is to add new primitives that feel native. Native here means that there are no syntactic differences between using a module you’ve created versus using a built-in module.

Languages that open source their evolution and allow new primitives that look built-in to users grow the fastest. Again, the maturation of Javascript is illustrative. In Javascript, it's easy to create and publish a new module. Any and every file could be a module. A random module is imported in exactly the same way as a "native" Javascript module. It's not a coincidence that Javascript has evolved from being used to draw pixels on a screen to being used serverside and now being used for native desktop and mobile applications.

Number of modules published per language over the last decade

There aren’t many surviving languages today that don’t have an easy way to publish new modules. One that comes to mind is APL — referenced in Guy Steele’s formulation of the above framework. APL was a promising language based on the premise that applying functions to arrays gave you a cleaner shorter syntax. However, APL struggled to gain widespread adoption mostly because its design was the brainchild of one person and it was challenging to extend via modules.

Final thoughts: what this means for you if you're building for developers

In my day-to-day, I spend a lot of time thinking about what makes for a great developer product and, consequently, a good investment. It turns out that a good developer product has a lot in common with a good programming language. This makes sense when you think of programming languages as the ultimate developer tool. So if you're building for developers, the shared drivers of adoption between programming languages and developer tools are worth paying attention to. I have written about some of these before. I'll recap a few key points here.

Adoption is a function of extrinsic factors and intrinsic factors. Platform shifts are an extrinsic factor. You don't control them. The growth of your developer product is a function of the growth of the underlying platform. What matters is aligning your value prop and messaging to emerging user needs on the new platform.

Extrinsic factors are only a part of the equation and apply to all products in a category. Developer products that go mainstream have superior intrinsic qualities.

Simplicity and composability are the two intrinsic qualities that matter most. They are a function of product/API design and should expand the skill range of developers who can use your product. The wider the skill range, the larger the TAM and the greater the odds of mass adoption.

The idea of a skill range for programmers is worth dwelling on, particularly regarding simplicity and composability. Skill range is a concept borrowed from gaming.[3] It has two essential elements 1) the skill floor: how easy is it for new users to get started 2) the skill ceiling: how complex a task a user can accomplish with your product. Simplicity lowers the skill floor for any developer to get started with your product. Composability increases the skill ceiling for developers to build more complex abstractions on top of your product.

In summary, winning broad adoption for a new developer product is a race to expand the skill range of your audience while riding a new platform wave.

Of course, this is all easier said than done. If you're building for developers and are thinking through adoption, I would love to hear from you.

[1]: I still recommend Java as a great intro language. The core OOP principles create a good structure for picking up useful concepts that show up in other languages.

[2]: Brendan Eich (Javascript creator) recounting the Javascript story is worth watching. I tend to think that Javascript was successful in part because it was created under pressure and had the right tradeoffs between simplicity and completeness.

[3]: One of the best articulations of this concept I have come across is Joel Quenneville’s reflection on who is empowered by proposed changes to the F# language.