My Manic Pixie Dream Programming Language
November 20, 2020 | 15 min. read
In the trades, the work being done often defines the tools that are used. Software engineering - being a kind of trade, despite how much its practitioners might be averse to the label - is no different. One of the most important skills in the industry is simply knowing what tool is right for the job at hand. In spite of this, however, it's nice to have one language that you know deeply and can use for miscellaneous tasks or tinkering with side projects where the main goal is just to have fun rather than develop bleeding edge technology.
I've used about a dozen languages across several paradigms so far (although I can only claim real competency with a few), and have yet to find one that fits like a glove. In this post I'll describe what my Dream Language would like like, what it would not look like, and some actual languages that have come close. Disclaimer: this isn't trying to be a thorough justification or critique of the features/languages presented here, but just a brief mention of why I like or dislike them.
Nice to Haves
Functional Primitives
The language that has probably most influenced how I think about
programming is Haskell. Viewing programs as composed transformations
of data rather than big stateful monoliths, in my opinions, leads to
much more streamlined and transparent code. As such my dream
language would have to include a few basic things that make this
possible. First class functions would be a necessity, in addition to
the usual map, fold, filter,
etc. standard library
functions to reduce boilerplate. Anonymous functions requiring as
little syntax as possible would also be nice, similar to
ES6's arrow functions or Haskell/Lisp
lambdas.
Algebraic Data Types
I almost regret learning languages that feature algebraic data types, because using languages that don't have any similar features is almost painful. Being able to create product and sum types is incredibly powerful, and so much more elegant that other features. To illustrate, if I wanted to create a type for a binary tree with ADTs I could simply do:
type TreeNode = Node TreeNode TreeNode Value | Leaf
and with one line we've captured the recursive nature of the
data structure, the possibility of null/empty tree nodes, and
the type of value that the tree stores. ADTs are even more
useful for error handling and type safety; the
Maybe
type in Haskell or Option
type
in Rust completely removes the the billion dollar mistake
from the languages. The Either
/Result
types in Haskell and Rust respectively also make error handling
mandatory and built into type signatures as opposed to exceptions or
error codes that can be ignored or invisible until they break
something.
Strong, Static Typing
This goes hand in hand with ADTs, but I'm firmly in the camp that
static typing reduces entire classes of bugs and allows me to feel
far more confident about the safety of my programs. Proponents of
dynamic type claim that dynamic typing allows for more rapid typing
since you don't really have to know the types of things until
something breaks, but I disagree. Static typing
doesn't always mean explicit typing (see C++'s
auto
or Haskell in general), and decent tooling and
documentation will let you know the types of things at a glance
anyway.
Garbage Collection
This probably has more to do with the domain in which I'm working
than my actualy preferences, but I'd rather have the computer do all
the work for me when it comes to memory management. I never work on
any projects with real-time constraints like embedded software or
game engines, so avoiding GC cycles for a few milliseconds of better
performance is just not worth the extra effort of tracking
malloc
s or deducing lifetimes. This isn't a win-win
tradeoff though, as sometimes garbage collection can be very
silly indeed.
First-Class Package Management
I can't tell what I dislike more - nonexistant package management,
or multiple competing package
managers. I would much rather have an included package and
dependency manager like cargo, pip,
or
gem
built in than have the freedom to choose between
have a dozen inferior implementations. Likewise, I just have better
things to do with my time than manually wrangle every dependency of
my project without a package manager.
Meta-Features
While not features of the language itself, there are a few things
that would definitely be dealbreakers. As a web developer I'd enjoy
a full-featured, batteries-included web framework like Django or
Rails or Laravel. An active community willing to answer
questions on StackOverflow or GitHub issues is also a plus, in
addition to accessible and up-to-date documentation. A
first-class tool like rustdoc
that generates
identical docs across packages is also super nice.
Nice to Not Haves
Object-Oriented Programming
This will probably be the most divisive opinion presented in this post. I don't have quite as radical an opinion as others on this topic - I don't think OOP is regressive or harmful or dangerous, it's just a different way to think about how to organize software. I do, however, think that drinking too much of the Object Oriented Kool Aid leads to an enormous amount of boilerplate and moving parts that are a nightmare to debug. Of course, drinking too much of the Functional Kool Aid can sometimes be a problem too. React Hooks, for example, really seem like a solution looking for a problem. Call me old-fashioned (meaning more than a year old when it comes to JS frameworks), but I'm more than happy to stick with class-based components.
In any CS101 class you'll be taught the basic class Dog
extends Animal extends Organism
example, and this is very
intuitive and abstracts incredibly well. In the real world, however,
I've simply never found myself needing to create any of the complex
hierarchies that OOP espouses. In cases where polymorphic
behavior is necessary, neat hierarchies never quite fit and
traits/typeclasses on structs have been more than powerful enough to
get the job done anyway.
Among the "hot" new languages of the past decade or so like Go, Rust, Elixir, and Clojure, most share a stark lack of traditional object-oriented programming features. I think this is a telling trend that the industry is tired of the dominance of the OOP paradigm, or at least willing to try something new in the decade to come.
Macro Systems
As a casual Lisp enthusiast, I can definitely appreciate homoiconicity and self-modifying code as a cool feature. A lot of the web frameworks I use similarly rely on macros for critical functionality. Ultimately though, I think that macros are more trouble than they're worth. At best, they encourage opaque black magic and the Lisp curse, and at worst you get the C preprocessor.
Significant Whitespace and Opinionated Formatting
These are semi-unrelated, but kind of similar in that I wish programming language designers would just leave me alone and let me have shitty (read: non-existent) style standards in peace. At the end of the day, it really shouldn't matter to the language whether I use tabs or spaces or how many spaces represent a tab or if I alphabetically sort my imports, and even being able to turn that off with line in a config somewhere is just one more thing to fiddle with instead of actually getting meaningful work done.
Go is probably the most egregious offender here. In no universe
should an unused variable be a compile-time, build-failing
error. I really don't care if I can just run gofmt
or it's automated or whatever - again, this is just one more
thing that I now have to include in my workflow that I'd rather not
worry about in the first place. In terms of "consistency" across
codebases, I really don't see how it should be the decision of
language designers rather than individual organizations on what
should be the default style or if there really needs to be one in
the first place.
The Close Contenders
Rust
For a lot of the things I want, I've pointed out direct examples straight from Rust. Also, considering how much I enjoy Haskell and how many features of Rust are borrowed from Haskell, it seems like Rust would pretty much fit the bill. Unfortunately, this isn't really the case. While the small project or two I have done in Rust have been fun for the most part, I just don't find myself needing the features in Rust that happen to demand most of my attention. I'm never really working on anything safety- or memory-critical enough to justify the amount of time I spend fighting the borrow checker, nor am I ever low-level enough to care about the performance gains from zero-cost abstractions. I'm still a very big fan of the language in general and won't hesitate to use it when the situation calls for it, but for the reasons mentioned it's certainly not my Dream Language.
TypeScript
This might not necessarily count since TypeScript is technicaly
just a type checker for JavaScript, but it seems fair enough to
call it its own language. TS ticks all my boxes when it comes to
typing - ADTs, strong typing, and first-class functions. TS also
inherits the absolutely enormous JS ecosystem (for better and
for worse), so there's more than enough of a community and
popular libraries. The fatal flaw with TypeScript for me,
though, is just getting a project up and running. Using
TypeScript in any meaningful way introduces an entire toolchain
and build system (Webpack, Babel, npm, etc.) that's hardly
reproducible without using pre-written scripts like
create-react-app
, and God help you if something
breaks. Again, I think TypeScript is a great tool and I use it
anytime I'm near the frontend, but I'd rather not touch a
webpack.config
unless I have to.
Go
I really want to like Go for a number of reasons. It seemed like the best of C and Python; it's performant, lacking bloat or legacy features (you can probably learn it in the space of an evening or two, especially if you skip the advanced concurrency stuff), has pretty nice built-in tooling, and there's a huge amount of libraries if you ever need to step outside of the fantastic standard library. However, there's just a few too many things about it that prevent me from really buying into it.
As I mentioned earlier, opinionated formatting is incredibly
obnoxious, and the language in general seems equally opinionated.
The documentation and community especially seems to give off the
vibe of, "This is the only way that you can do X, and you're
being naive by even bringing up something else."
Considering some of the opinions of its
,
"The key point here is our programmers are Googlers, they’re not
researchers. They’re typically, fairly young, fresh out of
school, probably learned Java, maybe learned C or C++, probably
learned Python. They’re not capable of understanding a brilliant
language but we want to use them to build good software. So, the
language that we give them has to be easy for them to understand
and easy to adopt." (1), and
"Syntax highlighting is juvenile." (2)
- Rob Pike, designer of Go
I'm confident that this is not me being overly sensitive.
The language really seems like it was designed by a rogue AI that was originally opitmized for simplicity. So many things would be much more tolerable with just a tiny bit more added complexity. For example, error handling is little more than repeating
value, error := functionCall()
if error != nil {
// handle error
}
several hundred times throughout a codebase. The missing functional
toolbelt like map, fold, filter
, etc. is also sorely
missed and the dogmatic approach of using for
for every
situation where iteration is necessary also adds totally
avoidable boilerplate. A lack of any kind of generics similarly
creates unnecessary work, although I must admit that the interface
system is usually enough to get the job done. A much more in-depth
blog post about some of Go's sharp edges with regards to its
fanatic approach to simplicity can be found here.
Conclusion
The logical thing to do after enumerating all the specific things I want and do not want in terms of language design would be to go about creating my utopian language. While I would like to be the change I want to see in the world, devoting what would likely turn into years of my life to a project with no guarantee of success just doesn't seem like a good allocation of my time. I'm more than happy to stand on the shoulders of people much smarter than me and grumble about minor language grievances while actually getting meaningful work done.
Considering I'm always trying to expand my knowledge and learn new languages and paradigms, this list will probably become little more than a snapshot of my current preferences in a year or two. There's a couple languages that seem promising that I might check out - Elixir and the Phoenix framework seem cool, and I've seen a lot of blog posts from the Nim team indicating some very impressive work on the language. I've never taken a deep dive into Prolog so maybe logic programming will be the second thing to revolutionize my views on software, or perhaps taking a trip to the source of OOP and learning Smalltalk might change my views on the object-oriented paradigm.
In the meantime, I'll just work on learning how to pick the right tool for the job. Or, at the very least, just pick what I get paid to use.