Languages are usually described in terms of being “high level” or “low level”. This is usually presented as a tradeoff, and as a programmer you’re obliged to pick your poison.
High vs. Low Level Programming
If you’re not a programmer, then I need to make it clear that these two concepts are probably the opposite of what you’d expect. A high-level language sounds like something for advanced programmers, and a low-level language sounds like something for beginners. But in classic engineer thinking, these paradigms are named from the perspective of the machinery, not the people using them.
A low-level language is said to be “close to the metal”. Your code is involved with manipulating individual blocks of memory and worrying about processor cycles. It’s very fussy work and it takes a lot of code to get anything done, but when you’ve got it written you can be confident that it will be incredibly efficientAssuming you’re knowledgeable and experienced, you didn’t create any major bugs, and the limitations of the target platform were made clear to you and were accurate. You know, the usual..
A high-level language allows you to express complex actions using very simple bits of easily-written code. It’s easy to write, but often wastes processor cycles and memory. How much? There are arguments all the time over whether the overhead for language X is significant or trivialAnd I’ll bet your viewpoint depends on your domain..
If you want to output the phrase “Hello World!” to the console, then here is how you do that using assembler, the lowest of the low-level languages:
SECTION .DATA hello: db 'Hello world!',10 helloLen: equ $-hello SECTION .TEXT GLOBAL _start _start: mov eax,4 mov ebx,1 mov ecx,hello mov edx,helloLen int 80h mov eax,1 mov ebx,0 int 80h
That’s a great big wall of inscrutable nonsense to perform a very simple task.
The BASIC programming language is extremely high-level and was a mainstay of computer education back in the 1980s. Here’s that same task implemented in BASIC:
100 PRINT "Hello World!"
That’s a pretty big difference in both size and readability. For reference, C++ falls somewhere between these two extremes. It’s considered a pretty low-level language by today’s standards. I know I’ve covered this topic before on the site, but for those who are foggy on the details, here is…
Yet Another Terrible Car Analogy™
You can imagine driving directions for a road trip from New York to L.A. The low-level version would tell you when to leave, what specific roads to take, when to stop for fuel, where to stop for food, and how fast to travel on each road to optimize fuel usage and synchronize with local traffic lights. A high-level version of the directions would be “Drive west until you hit the coast and then head south.”
The high-level directions are easy to write, easy to read, and are unlikely to contain any errors. The low-level directions are time-consuming to write, difficult to follow, prone to mistakes, but massively more efficient if they’re written properlyAnd in this case, “properly” means basically “perfectly”..
In computer science, this is usually portrayed as an either-or type deal. If you want to do things the easy way, then you have to abandon efficiency. If you want efficiency, it will come at the expense of more work. In C++ you can make your programs very fast and keep your memory usage low, but your code will be massive, dense, and difficult to maintain. In Java it’s easier to write and maintain the code, but you’ll waste a lot of memory and processor cyclesActually, the resources wasted are completely trivial and never something you have to worry about. Just ask a Java programmer..
What sort of language you use depends a great deal on the type of problem you’re trying to solve. If you’re writing an operating system or device driver, then you’ll probably want to use something low-level. If you’re writing something fun and simple with lots of user interface elements, then you want a higher-level language. In my personal experience, writing interface code (dialog buttons, lists, scrolling text boxes, etc.) are pure torture to write in a low-level language.
The problem with gamesAnd maybe lots of other domains. I dunno. I can’t speak for them. is that you kind of need both. You want the guts of your rendering pipeline to be written as close to the metal as possible. On the other hand, a lot of your code is going to be dedicated to expressing the rules of the game: Hitpoints, damage, inventory, etc. That code usually isn’t very costlyDepends on the genre, obviously. in terms of processor cycles. It doesn’t need to be expressed in low level terms, and it’s often extra work to do so.
So Let’s Use Two Languages!
Developers have sometimes routed around this problem by using two languages. C++ will be used to make the costly stuff like rendering, physics, and audio. Then the gameplay stuff will be offloaded to a scripting language like UnrealScript, Papyrus, or LUAThere’s also a second reason for using a scripting language, which is that it allows end-users to make gameplay mods without giving them access to the engine.. This allows the developer to use the harder language when speed counts and an easy language when it doesn’t, although it has the obvious drawback of having the game implemented in two different languages. Also, you can’t really shove all of your performance-critical functionality into a box like that. In a complex system, there’s not always a clear line between the “engine” and the “game”.
Sometimes you’ve got a lot of game objects (like particles) that are so numerous that you have to think very hard about how they’re processed and you need to be very careful with their memory footprint. Sometimes you’ve got other game objects (like a bounding box to trigger a cutscene) that are few in number and require no special performance considerations. Sometimes objects start at one extreme and migrate to the other as your design changes. It really sucks if you realize you need to migrate some code from one side of the wall to the other, because that means re-implementing everything in the other language. It would be nice if we could make the transition from “convenient to write” to “fussy and finely-tuned” without needing to rewrite all of the code in a different language.
I spent some time learning C# and Unity last year, and I found myself questioning the inevitability of this tradeoff between processing efficiency and programmer expressiveness. I was amazed at how much faster it was to work in an environment with so many convenience features, but then I’d run into a situation where the language wouldn’t let me ask simple questions or take reasonable steps to ensure performance. These two things seem to be orthogonal, and I don’t see why we can’t have both.
Okay, I can understand why we can’t do both at the same time. But I don’t see why our language must limit us to one particular level of expression. Sometimes it would be nice to just PRINT “Hello World!” and other times you need to crawl down into the guts of the engine to allocate and manually manipulate individual bytes of memory.
In computer games, you don’t always need speed and ruthlessly frugal memory management. But when you do need those things, you really need those things. As the program grows in complexity and the performance needs become more clear, more of the systems will need to migrate from the “easy to write and manage” side to the “fussy and difficult to understand” side of things. Not only do you have to re-write the code in the new language, but you have to build a bridge between the two so your slow-ass scripting language can hand a tricky job off to your cryptic low-level language. And if you’re dealing with complex objects with lots of data – like Space Marines or space orcs – then you have to describe that data in both languages and keep those bits of code synchronized.
This isn’t an impossible task or anything. Coders do it all the time. But it would be nice if you didn’t have to.
A language designed specifically for games could perhaps allow us to be exacting when performance is crucial, and expressive when it isn’t. Sometimes you need direct memory access so you can worry about the placement of individual bytes, and sometimes you just want to process some simple data without having to think about the hardware.
The second most controversial thing that Jon Blow has said about Jai is that it’s a language designed for “good programmers”. (The most controversial thing was his announcement to build the language in the first place.) This rubbed a lot of people the wrong way, since it seems to imply that all those other languages are for “bad programmers”.
Direct memory manipulation is a classic example. It’s tough to do properly, and very easy to make mistakes. The compiler usually can’t help you identify mistakes in memory management, which leaves you to face the problem on your own. Worse, memory management problems cause catastrophic bugs like crashes or slowdowns.
(Direct memory manipulation is a bit of an oversimplification for what I’m talking about here. I’m not just talking about allocating memory and managing it yourself, I’m also talking about allowing the programmer to specify the layout of things in memory, gain direct memory access to things their code didn’t allocate, and copy arbitrary blocks of memory from A to B.)
Back in the bad old days of C, this problem was really common. Every single string manipulation required manual memory management. Even if you just want to print “Hello World” to the screen, you need to take responsibility for those 12 bytes of memoryOne byte for every character in the message, plus one extra to mark the ending of the string. As you can imagine, that “one extra byte” business made it very easy to make subtle mistakes. and keep track of them yourself. If you make a mistake in managing memory like this, you’ll generally crash or experience bewildering buggy behavior. It’s like trying to prepare a sandwich when you don’t have pre-sliced bread and your only knife is an industrial buzzsaw. This is a simple task. Why does it need to be so dangerous?
Some languages try to protect the programmer. They impose structures and syntax to make it difficult or impossible to make these kinds of blunders. Effectively, the language takes away your buzzsaw. When coders complain that they need a buzzsaw, people explain that you shouldn’t be using a buzzsaw anyway because they tend to cause more problems than they solve.
I don’t like the narrative that the world is flooded with idiot programmers, but it is an observable truth that software has a lot of bugs in it, and statistically those bugs are the result of a small handful of common mistakes. It makes sense to take those hard-learned lessons and use that knowledge to build safer languages in the future.
This leads back to the classic programmer argument:
Alice: This tool is terrible! Look at how many problems it causes!
Bob: The tool is fine. You’re just bad at using it!
Not every company can afford to hire expert-level coders for every language. Sometimes the most qualified person to maintain that legacy C code is the woman with two decades of Java experience. It would be nice if we could afford our own cryogenically frozen greybeard C coder and we could thaw him out every couple of years when something needs to be changed, but the only thing more expensive than cryo tanks is greybeard C coders.
So we build a language that takes away the buzzsaw and gives the coder safety scissors. That won’t help us maintain this ancient C code, but it will help us to avoid leaving these sorts of messes for future coders.
But the buzzsaw DID have legitimate uses, and if you take away the tool entirely then you forever lose the ability to solve those problems. That helps all those other programmers who struggle with the tool, but it leaves you weaponless when you come up against a problem that requires it.
So we have this perceived trade-off between expressiveness and performance. When it comes to manipulating strings, C++ offers a great example of a case where we can have both. The C++ standard libraries offer specific types for manipulating strings. You can use these types when you just want to shuffle some text data around when you’re not worried about performance. If you are concerned about performanceLike, you have a program that needs to chew through gigabytes of log files for whatever reason., you can always pull out the buzzsaw and go back to the classic C style of handling text. I can imagine a language designed for games that offers this sort of choice everywhere. It would be a complicated language, but it would probably be less complicated than two unrelated languages being used in tandem, which is where we are right now.
Those safety-scissor languagesPlease don’t take this as mocking towards those languages. I’m not saying languages like C# or Java are for kids or that you can’t do serious work with them. I’m just trying to explain this divide without throwing too much jargon at the non-technical readers. are designed with the built-in assumption: “The programmer might make mistakes, so let’s limit their ability to hurt themselves.” Saying Jai is designed for “Good Programmers” doesn’t mean people using those other languages are lesser programmers, it means the language is going to assume the programmer knows what they’re doing and isn’t going to try to save them from themselves. Jai programmers will be just as flawed and error-prone as programmers in other languages because we’re all human, but Jai is designed to allow you to make those mistakes because sometimes a buzzsaw really is the best tool for the job.
Shamus, doesn’t it seem sort of rude to refer to the language as being for “good programmers”?
Doesn’t it seem sort of rude for all these other languages to assume you’re not a good programmer? If we’re going to get offended by the assumptions built into these languages then we’ll never run out of things to get mad about.
Again, domain is everything. A chef shakes his head and talks about the crazy old times when they had to make sandwiches with buzzsaws, while the carpenters are like, “Why does it need to be so hard to get my hands on a simple buzzsaw these days? Do you have any idea how hard it is to cut through oak with a bread knife?”
 Assuming you’re knowledgeable and experienced, you didn’t create any major bugs, and the limitations of the target platform were made clear to you and were accurate. You know, the usual.
 And I’ll bet your viewpoint depends on your domain.
 And in this case, “properly” means basically “perfectly”.
 Actually, the resources wasted are completely trivial and never something you have to worry about. Just ask a Java programmer.
 And maybe lots of other domains. I dunno. I can’t speak for them.
 Depends on the genre, obviously.
 There’s also a second reason for using a scripting language, which is that it allows end-users to make gameplay mods without giving them access to the engine.
 One byte for every character in the message, plus one extra to mark the ending of the string. As you can imagine, that “one extra byte” business made it very easy to make subtle mistakes.
 Like, you have a program that needs to chew through gigabytes of log files for whatever reason.
 Please don’t take this as mocking towards those languages. I’m not saying languages like C# or Java are for kids or that you can’t do serious work with them. I’m just trying to explain this divide without throwing too much jargon at the non-technical readers.
Another PC Golden Age?
Is it real? Is PC gaming returning to its former glory? Sort of. It's complicated.
Mass Effect Retrospective
A novel-sized analysis of the Mass Effect series that explains where it all went wrong. Spoiler: It was long before the ending.
Was it a Hack?
A big chunk of the internet went down in October of 2016. What happened? Was it a hack?
PC Hardware is Toast
This is why shopping for graphics cards is so stupid and miserable.
Marvel's Civil War
Team Cap or Team Iron Man? More importantly, what basis would you use for making that decision?