Microfeatures I'd like to see in more languages
There are roughly three classes of language features:
- Features that the language is effectively designed around, such that you can't add it after the fact. Laziness in Haskell, the borrow checker in Rust, etc.
- Features that heavily define how to use the language. Adding these are possible later, but would take a lot of design, engineering, and planning. I'd say pattern matching, algebraic data types, and async fall under here.
- Quality-of-life features that aren't too hard to add, and don't meaningfully change a language in its absence. Often syntactic sugar, like Python's chained evaluators (
if 2 <= x < 10
).
Most PLT and language design work is focused around (1) and (2), because those are the most important, but I have a deep fondness for (3)-type features. Because they're so minor, they're the most likely to spread between languages, since the overhead of adding them is so small. Since I spend a lot of time in niche obscure languages, I also encounter a lot of cool QoL features that most people might not have seen before. Here's a few of them!
Number Representations
There's lot of small things we can do to make working with numbers easier. The first is adding a separator, as lots of languages already do. Instead of writing 10000500
, you can write 10_000_500
, or 1_00_00_500
if you're Indian. You can also do 1e3
instead of 1000
.
For more interesting number QoLs, we can look at J. In science and math you sometimes get equations with powers and roots of π. J has the standardized number format {x}p{y}
for xπ^y
. For example, you can write 5*sqrt(π)
as 5p0.5
. There's also x
for powers of e
and r
for exact rational numbers ((2r3 + 1) = 5r3
).
Balanced string literals
In Lua you can write raw, multiline strings with [[]]:
[[
Alice said "Bob said 'hi'".
]]
Most languages have multiline literals, but what makes the Lua version great is that the beginning and ending marks are different characters. This solves the infuriating "unnestable quotes" problem string literals have, and you don't have to escape all your literal \
s. My neovim has the string [[\\]]
to literally mean the string \\
. With escaping that'd be "\\\\"
or something, ick.
Generalized update syntax
I saw this in Noulith and immediately fell in love:
Ever wanted to write
x max= y
while searching for some maximum value in some complicated loop? You can do that here. You can do it with literally any function.
In other words, x max= y ⇔ x = max(x, y)
. It should conceivably be extendible to things like text sub= (regex, replacement)
, which would cover a lot of small munging and plumbing work I'd need to do.
The Chapel Power Hour
Chapel is a language for high-performance computing, where you need to run really fast algorithms across hundreds or thousands of processors. It's got a ton of (1) and (2)-type features I've never seen anywhere else, and I find the language incredibly interesting. Even not knowing it, there's a few QoL things I found in the docs that I now want in other languages.
First, there's the config
keyword. If you write config var n=1
, the compiler will automatically add a --n
flag to the binary. As someone who 1) loves having configurable program, and 2) hates wrangling CLI libraries, a quick-and-dirty way to add single-variable flags seems like an obvious win.
Second, you can write the sequence 1, 2, … n-1
as 1..<n
. It's an elegant extension to the standard ..
operator languages like Ruby and TLA+ use.
Third— and this is one is a lot more arguable— you have "promotion", a sort of automatic type lifting. The gist of it is that if you have a function of type a -> a
, Chapel lets you call it with arrays, too, returning a mapped array. So if f(x) = 2*x
, then f([1, 2, 3]) = [2, 4, 6]
. This is completely type-safe, too. You can get the same effect in other languages with a lift
or a map
, but it's a nice QoL to make it native.
(Chapel has an extra flex here: it automatically parallelizes the computation. That's definitely outside of (3) territory, though).
These are just a few of the cool features in Chapel, and I'm not even touching the really fundamental things. I enthusiastically recommend anybody interested in PLT or scientific computing to check it out.
Date literals
Frink has special syntax for date values. You can write # 2001-08-12 #
to mean the date 2001-08-12, instead of writing something annoying like Date(2001, 8, 12)
and having a bug because months (but not days) start at 0.
Expanded Parameters blocks
Powershell functions can have an explicit Param
block where you define attributes for each function parameter, like default values, help docs, validation constraints, etc. So instead of writing fun f(str path = "/", int x)
, you can write something like (in pseudocode)
fun f {
params (
[default = "/"]
[help = "The path to your file"]
[mandatory]
[str]path
, [optional][int] x
)
}
Now this makes a lot more sense for a shell language, where you want lots of optional flags, switches, and aliases, but I can see it being really useful in a compiled language too. For one, it makes adding variable-level contracts easier. Second, what if parameter blocks were abstractable? If you look at something like numpy functions, so many of them share the exact same parameter definitions. What if you could write def log(standard-exp-params)
instead of having to write them out every single time?
kebab-case
As seen in most lisps. Instead of naming things two_words
or TwoWords
, you can use the name two-words
. It's easier to write and easier to read. Of course the reason why nonlisps can't do that is because they have infix minus, and it's ambiguous whether x-y
is the expression x minus y or the invocation of the x-y
function. Seems like a bad tradeoff, though. How often do you use -
, and how often do you write multiword functions? Just say that x-y
is always a function, and if you want math you can use spaces like the rest of us.
(Granted this couldn't be added to existing languages without breaking everything, but maybe worth considering if you're making a new language?)
Symbols
Ruby has a special data type called a symbol, written like :this
.1 A symbol compares equal to itself and has no other functionality. It replaces single-word strings— instead of writing dict["employee_id"]
, you write dict[:employee_id]
.
The advantage of having symbols is that it makes strings easier to work with. In most languages, strings are used to represent a lot of different things: tokens, text, structured data, code, etc. If you see the string "book", it's not clear without context whether it's a dictionary key, or a text field with just "book" in it, or a trivial CSV, or what. With symbols, you can at least rule it out the former case, because then it'd instead be :book
.
Dedicated testing syntax
As seen in D's unit-test blocks on functions and P's monitors. While it makes sense to keep the actual testing as library code, testing is so universal in larger software projects that it sounds nice to give it syntactic support.
Anyway I don't think adding these to arbitrary languages would be easy or have no downstream impacts, but they all at least seem more orthogonal to existing language semantics than type-(2) features, which are often back-added (for lots of good benefits). So they'd more easily to spread across languages. Except kebab-case. We live in a fallen world.
Update for the Internets
This was sent as part of an email newsletter; you can subscribe here. Common topics are software history, formal methods, the theory of software engineering, and silly research dives. Updates are every week. I also have a website where I put my polished writing (the newsletter is more for off-the-cuff stuff).
-
Symbols come from lisps, but I encountered them in Ruby first. ↩
If you're reading this on the web, you can subscribe here. Updates are once a week. My main website is here.
My new book, Logic for Programmers, is now in early access! Get it here.