Advent of Computing - Episode 148 - Is BLISS Ignorance?
Episode Date: December 22, 2024In 1970 a little language called BLISS emerged from Carnegie Mellon University. It was a systems language, meant for operating systems and compilers. It was designed, in part, as a response to Dijkstr...a's famous Go To Considered Harmful paper. It had no data types. It used the most bizzare form of the pointer I've ever seen. And it was a direct competitor to C. Sound interesting, yet? Selected Sources: https://bitsavers.computerhistory.org/pdf/dec/decus/pdp10/DECUS-10-118-PartII_BlissReadings_Dec71.pdf - Readings on BLISS https://www.cs.tufts.edu/~nr/cs257/archive/ronald-brender/bliss.pdf - A History of BLISS
Transcript
Discussion (0)
There are a few technologies that mark out key points in the history of the computer.
These are keystone events that set the stage for the future.
I'm talking things like the EDVAC report, which leads to a recognizable form of stored
program computers.
Or the launch of the IBM PC, which sets in stone what home computers would look like
for decades to come, or IBM's System
360, which pushed us into the 8-bit byte as a standard.
If any of these events happened slightly differently, well, the story of the computer would be very
different indeed.
One of these keystone events happens in 1972.
That's the creation of the C programming language.
Almost every programming language in modern use is somehow related to or influenced by
C. It's how curly braces become popular. It's why, if you know Python, you can pretty
quickly pick up JavaScript. It's the root of so many current lineages.
I think you'd be right to say that if C never happened, programming would look and be very
different. Then, what could programming have looked like without C?
Now, that's really hard to say. C becomes a phenomenon for a number of reasons.
However, there were a number of languages that were developed around the same time as
C and had very similar design goals and purposes to that of C. These were high-level system
programming languages.
Tools meant for writing operating systems and compilers. These powerful compromises between the control of machine code and the flexibility of a high-level language.
C itself evolves from some of these earlier attempts, but there are separate lineages here
unrelated to this popular language.
It's easy to imagine a history where C is developed but never takes off,
where it's killed in the cradle by one of these unrelated systems languages. In that
case, the future of programming may have taken on aspects of the victor, that is, if we assume
that this one fight in the 1970s leads to modern programming. If we follow that assumption,
then looking at C's competitors could give us a glimpse of that alternate future.
Welcome back to Advent of Computing. I'm your host Sean Haas, and this is episode 148, Is Bliss Ignorance?
This episode will be taking a look at a failed language. Now, I will say a lot of failed languages aren't that
interesting. There are some that are just idle passing fancies. There are some that are just amateurish.
are just idle passing fancies, there are some that are just amateurish.
Not cool except for maybe a little pointing and laughing, but even that's in bad taste.
This language, however, I think stands closer examination.
The language we'll be discussing today is called BLISS.
It was developed in 1969 at Carnegie Mellon University.
It was intended as a portable systems programming language.
It was derived from ALGOL.
It competed directly with C, and it lost.
What I find interesting is that the goals of BLISS align almost word for word with the
goals of C. The key difference from the outside
is that C was developed at Bell Labs. But there must be some deeper difference here,
right? Why did bliss fall to the wayside as C rose to prominence? Was it a matter of place
and time, or was bliss somehow missing a killer feature that made C a winner?
In this episode, I want to talk about Bliss as its own language, but at the same time,
I want to work out what made it different. Why was it considered a competitor to C? What
did Bliss do that was better than C? What made it worse? And how did it eventually lose?
In doing so, I hope we can work out a few
things about good program language design. But for this one, I'm going in totally blind.
So who knows, maybe it's that C is the bad language and Bliss was wrongly overlooked.
Why does that matter? Well, it all comes down to the power of C. Most modern languages are somehow related
to C. If you've ever seen a language with curly braces, chances are it is directly related
to C. The life of a modern-day programmer is very deeply shaped by choices made during
the development of the C programming language in the early 1970s.
That makes the idea of a counterfactual pretty darn exciting.
What if C failed?
What if it was different?
Or what if it was beaten out by an entirely different language?
That language very well could have been Bliss.
Now before we get into the episode proper, I have actually three announcements this time.
The first is that I'm currently in the process of working up a bonus episode over on Patreon.
Right now I have a thread up where I'm taking suggestions for a bonus episode for I think
maybe another week, maybe two weeks, depending on how active it is. I'm then going to take the
three most popular ideas in that thread and put it to a poll for the patrons.
There's still a little time to get in on deciding the bonus topic. For that you can head over to my Patreon.
Announcement two is that on February 8th, I'm going to be speaking at Intelligent Speech.
This is an online conference for history podcasters to get together and give talks on a specific topic.
This year the topic is deception.
It should be a lot of fun. I spoke last year and had a great time.
So if you want to join in on that, you can get tickets at IntelligentSpeechOnline.com.
And announcement number three is that I'm gonna be at VCF SoCal this February.
I know, I'm making the rounds for once
VCF SoCal is happening February 15th through 16th in Orange, California It's right near LA you can actually fly into Burbank or LAX and get over to the conference pretty easily
That event is taking place at the Hotel Farah,
which is a big conference center.
If you want to attend that, you can actually get special room rates
through vcfsocal.com to stay in the same hotel
and walk right down to the convention floor.
It's actually a really, really nice setup.
Right now I'm planning to give a talk on some software restoration at VCF SoCal.
I'm also going to be milling around the conference all weekend.
So if that sounds fun, I'd love to hang out.
Come show up, we can meet up, and we can have a great time.
That once again is happening February 15th and 16th in Orange, California.
Okay, all of that done, so let's start the episode itself.
Computer programmers are a little, well, let's say dramatic.
That might be the most reasonable word to use here.
I think this has something to do with how programming kind of warps the human mind.
Because of that, we tend to get some pretty spicy papers and letters to the editor of various journals and magazines.
One of the all-time greats in this genre is Edgar Dijkstra's go-to statement considered harmful.
The paper hits print disastrous effects, and
I became convinced that the go-to statement should be abolished from all higher-level
programming languages, end quote.
Dude doesn't like the go-to, and he argues that you should also hate it.
But what exactly is the deal here?
The go-to, on its own own is a pretty innocuous concept.
It tells a program to jump to some predefined label.
You label some piece of code, then you tell the program, hey, you remember that part of
the program I told you about?
It's time to go to that.
That simple.
This is a very low level way of thinking about programming.
It's actually how computers think.
On the processor level,
most logic is built out of a series
of what are called unconditional jumps.
That's the exact same as a go-to.
This is backed up with some conditional jumps,
which are like having a go-to inside an if statement.
Early on, many high-level languages also supported this type of jumping. Fortran, for instance,
lets you run a go-to. Basic is, perhaps, famous for its use of the go-to statement.
On its own, a go-to is fine. Well, on its own with a label around, but you get the idea.
Where we get into dicey territory is once we start looking at everything that
can be around the go-to. For a very simple program, a jump is fine, it's just
a jump. What you often want is to only execute a part of a program if some
condition is met, and otherwise execute some other part of that program.
For that, you can chain together some if statements with a few go-tos.
If the light is green, then go to the label called start.
If the light is red, then go to the part of the program called stop.
Very simple.
But what happens if, say, you have a go-to in a more complex program?
Let's say, by way of example, you have a loop that contains a go-to statement.
How should the language handle that?
You're basically terminating that loop early and going to some other part of the program.
That's perhaps a little strange, but we can probably all work up a solution
to that problem, right?
You're just exiting that loop a little early,
but what happens to variables inside that loop?
What happens to the scope of variables when you jump?
What happens to global variables that have been modified
when you jump outside a loop?
You get into all these tricky gotchas that you have to think about and plan around in the language. Now let's flip that example.
Let's say you have a label inside a loop. Then you jump to that label. You're jumping right to the
middle of a loop, effectively entering the loop from the side. But it's not that simple.
Loops all have to start with some kind of setup routine. It's usually just a
header that says, do this while x is less than 10, or something similar. If you
jump to the middle of a loop, then you are, in effect, entering that thing prematurely, the setup hasn't been done.
So what happens?
If the loop is supposed to run 10 times, but you jump into the middle, does it run 10 and
a half times?
What if the loop assumed that x would start at 0, but you're jumping from some point where
x is 1000?
Or x is minus 10, or x is some
other non-numeric value. Suddenly you have to design a pile of edge cases or
build out a robust error handling system, or maybe have go-tos that can jump
anywhere, but no not actually there, you have to have some restrictions. Go-to
considered harmful was published in Communications of ACM and quickly became a
hit.
Over the years, it's become something of a meme amongst programmers, but its core idea
is sound.
Go To was implemented in most languages prior to 1968, and it introduces a host of problems
and inconsistencies.
This isn't to mention things like optimization, which becomes almost impossible
if you don't know the flow of a program.
One of the people impacted by Dykstra
was one Dr. William Wolfe,
who at the time was a researcher
at Carnegie Mellon University.
As Wolfe would later explain in an oral history interview,
quote, I bought into that and decided we should design this language, Bliss, without the go-to statement.
It was clear theoretically that you could do without it as long as you had other control
structures like if, then, else, and then various iteration statements and subroutine calls,
but it wasn't clear that as a practical matter that was going to be a good idea."
Now, to be 100% clear here, there were already some languages without go-to statements.
Lisp and its family offer one example. That said, Lisp-like languages have always existed
kinda in their own world. They're purely functional,
which is a totally different approach to design. Most other languages, from Fortran to Algol,
from BASIC to PL1 to COBOL, used the go-to. The idea of a language built without go-to that wasn't a
Lisp relative was an enticing line of inquiry. But that wasn't Wolf's only motivation.
The Dykstra paper would serve as something of a jumping-off point to get Wolf into a larger
project he had been considering. In this same period, there was a hot debate over programming
operating systems. This is sometimes called systems programming, that is, very complex
code that sits close to the computer itself. The key argument was, should operating systems
be programmed in high-level languages? For the 1960s, this was high controversy. It was
traditional to write operating systems in assembly language or machine code. This was
done at first because you just didn't have any other options. High-level languages
don't really appear until the late 1950s. When they do, they aren't very well
suited to systems programming. Why wouldn't a high-level language work for
this application? It all comes down to control. Systems programming requires very precise
code and total control over the computer. You have to do things like juggle registers,
move around blocks of memory to precise locations and handle physical interfaces. The whole
point of a high-level language is to pull the programmer out of all those mundane details. A language like Fortran was designed to protect you
from all sorts of low-level nasties.
But it's that exact nastiness that you have to face
when you write an operating system.
This leads to what seems like a paradox.
High-level languages are meant to stay out
of the low-level realm of the machine.
So how do you use a high level language to write low level code? What's even the point
of attempting such a feat? There are a lot of good reasons to break from tradition and
use a high level language for this kind of software. I know this from experience. I've
written a few very bad operating systems
in my day in both assembly language, a low-level language, and C, a high-level language. When
you work in a high-level language, you can use less code to do the same thing. Fewer
lines of code means fewer bugs, and it means that the bugs you introduce are easier to
track down. You also get access to much more sophisticated features. This varies language to language.
For me, the jump from assembly to C gave me reusable data structures, data types, and actually
visible pointers. That's not to mention that high-level languages are just more readable
and easier to maintain.
By 1968, there were a number of projects that were attempting to make this jump. Burroughs
had an operating system written in a dialect of Algol. So did GE. There was also the ongoing
Multics project, a full time-sharing operating system written in PL1. We're solidly in the emerging phase of this technology,
and Wolf wanted in. Sometime around 68, he started his plans for a new operating system.
The first step, though, was choosing the high-level language to use. After a survey, he landed
on an exciting conclusion. None fit his criteria.
As he would later write in his first paper published on Bliss,
quote,
we have chosen to add to the tongues of Babel
by defining yet another new language
and some justification is required.
The only valid rationale for creating a new language
is that the existing ones are inappropriate to the task.
It was our judgment that no existing language are inappropriate to the task. It was our judgment that no
existing language dealt with all the proper issues and hence a new language was necessary."
End quote. The go-to-less aspect being front of mind made its way into the list of requirements.
I honestly think that might be one reason that Wolf really chose to create a new language.
All the competitors had go-to.
The rest of the list has to do with systems programming, pure and simple.
But keep in mind, this was an emerging field.
There wasn't some traditional and enshrined set of requirements for this kind of language.
There is plenty of space for discovery here, which is what makes these kind of times so exciting. Here I'm gonna be
cherry-picking from some of Wolf's line items, because in his paper on Bliss he
gives a straight-up list of design requirements. That's really handy to use.
The first item, and probably the most obvious is quote access to all
relevant hardware features. To do systems programming you need to have some way to
control the system. That's a simple enough idea right? But what does that
mean in practice? This usually means allowing some kind of direct memory
access. Yet again memory is the heart of the computer.
But this isn't just to let you push around bits and make funny little images.
Many computers represent I.O. devices in memory,
or have special structures in memory for setting up things like interrupts or data channels.
So if you have fine-grained control over memory, you have
nearly total access to the hardware of the machine. That's the route that Bliss would
go.
The next line item is that a system language should have no need for runtime support. What
does this mean? Well, most normal languages are targeted to run under something like an
operating system.
That means that the code, once compiled, expects certain tools to be ready and waiting for
it.
If your program prints something to screen, it's using runtime support.
It's calling out to the operating system while it's running and asking for a little help.
But what happens if you're the operating system itself? What if you are the clown pagliacci?
In OS land, you have no runtime support. You have to bring your own runtime support. You have to
make yourself laugh somehow. So a systems language cannot rely on runtime support. That means you
won't find things like print in bliss. You have
to work out how to do that yourself. The next item is control of representation.
This goes hand-in-hand with hardware access. When a computer offers memory
mapped stuff, that doesn't just mean location matters, it also means that
structure matters. An IBM PC, for instance,
wants a very specifically formatted table at a very specific address for
setting up interrupt handlers. How does control of representation help this? What
does that even mean? Think about this in terms of data structures. If you have
control of representation, then you can define data structures with a very
specific pattern in memory.
Then you can create a structure that matches that PC's interrupt table.
You could graft that table from the computer's spec sheet into your language and then manipulate
it using your language.
Sometimes you'll hear this concept, the representation of a data structure called its shape.
At least, that's the term I use at work.
Those are the straightforward and obvious line items, but the list keeps on going.
This is where we pass into more innovative territory.
The first is modularity. That is, chunks of code can be compiled into
modules that can later be loaded by another program. On one hand, this is just practical
program design. Modularity lets you reuse little helpful programs that are already compiled and
ready to grab. You just take the binary, plug it into your new program, and you're ready to go.
Now, the documentation around modules in Bliss
is a little cagey,
and I don't wanna go down the route of learning
and working with Bliss right now,
so I'm gonna guess.
The reason I said that modularity is an innovation here
is because I think Bliss
can pull a trick with modules. Keep in mind this is from reading papers and
spec sheets and extrapolating based off what I've seen in similar languages. I
think Bliss should be able to do dynamic module loading. At least my understanding
points to Bliss having all the features needed for dynamic modules.
That means that while running, a Bliss program could grab a module from disk, load it into memory,
and plug it into a running program. How Bliss handles communication in and out of modules, plus
some of the weird stuff about its pointers, make me think that that kind of module loading would be pretty simple in the language. But hey, this is my guessing. There'd be a lot of actual support you'd have to write to
do that. Now, the next design feature is conditional compilation. This one is also weird. The general
idea is that you have some way to control what the compiler chooses to compile.
You can put in some special code that, given some information at compile time, will either
be included or excluded from the final program.
On the surface, why would you want that?
You always want all your code compiled, right?
Well, not necessarily.
The most simple case here is debugging code.
It's common to have a complex debugging library built up
that helps you debug your program.
That can take up a lot of space
and add features that only you as the programmer need.
When it comes time to compile and ship your program to users,
you don't wanna include all your debug code.
With conditional compilation,
you can just tell the compiler to ignore that part of the program.
Thus, your dirty secrets are kept safe from prying eyes.
Bliss takes this a step further.
It introduces this concept of a compile time code and runtime code.
As in, code that's executed
while the compiler is working and code that's executed when your final program is running.
This is something that we don't have enough time to get into, sadly. The short story here
is that Bliss has a very, very sophisticated macro system. It's so complex that you can write
an entire program only using macros. That's, like I said, very complicated. It's something
I'm choosing to gloss over in the interest of time. Now, the last item I want to hit
on is actually a sublist just titled, overall good language design. I mean, yeah, sure,
all languages should have overall good design, right? The details here are pretty standard,
things like readability and well-structured code. I want to just drop one thing from that
list that I think is particularly interesting. Quote,
economy of concepts, generality, flexibility. End quote.
To me, that sounds like a concept we've talked about before.
Orthogonal design. The idea was core to the design of Algold 68.
In short, in orthogonal design, you try to have a minimized set of primitives in your
language that you use together to build up the rest of the programming language.
To do this, you need to pick simple primitives that can work together.
Algol 68 is very, very orthogonal.
Like I said, it was a core design goal.
Bliss isn't quite at that level, but the language has
that orthogonal feel to it. In fact, in later papers, Wolf even mentions that he was trying
to go for an orthogonal design.
Now there is one other secret design goal. That's machine independence. That right there,
that should set off some alarm bells. How do
you make a system's programming language that can be independent of the
underlying system? Besides the paradox that introduces, it also puts bliss on a
direct road to conflict with another language. But that conflict is still a
few years off when Wolf decides on these design goals.
When you write in assembly language, the main thing you're actually programming is memory
manipulation.
You might have some cool game or tool in mind.
You might have some neat algorithm you want to implement.
But at the end of the day, the majority of your assembly code is just pushing around bits and building data structures in memory. When
you're down in that low-level world, you don't get much help with, well, really
anything. One of my favorite realizations came a few years ago. I
usually write assembly language for the PC without any sort of runtime environment.
I prefer the bare metal, as it's called. That means no Microsoft DOS, no real libraries,
nothing. Well, I decided to write my first DOS program a few years back, still in assembly,
of course. Now, DOS offers some useful runtime support. It will print to the screen for you. It will read
and write files from disk. All the classics. What it doesn't give you, however, is any kind of help
pushing around bits. Even with DOS's runtime, you still have to manually handle memory. I remember realizing that,
chuckling,
and going back to my bit-bashing ways.
Yet again, memory is key.
This is why it's so crucial.
It's such a big deal
when a language supports data structures.
I personally like the dumb bean counting
I have to do in assembly language
because I don't do it professionally. It's a fun little hobby to do, like how some people
paint minis or work on a car. But when it comes to actual programming, it's a pain.
It's error prone, it's annoying, and it's very difficult to do correctly. When you jump to a high level language,
you can just have, I don't know,
an integer variable or a string or something more complex.
That's a huge leap forward.
You don't have to worry about the beans anymore.
All this is to say that data structures are very important.
Variables and types are very important. Variables and types are very important.
Bliss has no data types.
It's what's known as a typeless language.
What's interesting is that I can only think of a few other typeless programming languages. You have Forth, BCPL, B, and this
weird cursed thing called PlanCalcool. To say it's an uncommon choice is an understatement.
Typelessness is rare, strange, and wondrous to behold.
This gets weird quick, so I want to start from basics. I know I keep calling
back to the Zeus episodes, but I can't help it. Zeus has managed to worm into my brain,
so to everyone that said I should have covered Zeus much earlier, you were right, you win.
Now I'm cursed. In PlanCalcool, Zeus's programming language, there's only one data type, the bit.
You start from there, and you're expected to build up data structures by composing bits.
That might make a structure called an integer that's composed of 32 bits, and you might treat that like a whole number.
But the language itself has no enforcement
of that structure.
It has no concept that an integer is 32 bits.
That enforcement part is crucial here.
That integer structure that you create isn't anything special to plancalcul.
There's nothing stopping you from treating it like a 16-bit structure by accident or a character or something else that would make no sense at all.
Bliss's type-free system works in a very similar way. It's not that you have no types, it's
just that you only have one. In Bliss, that's the so-called full word. It's, well, just as it says.
It's whatever the host computer uses as a word of data in memory.
For the PDP-10, for instance, a full word would be 36 bits.
When you define a variable, it's allocated as a single word.
You just get 36 bits. You, as the programmer,
are expected to use those words as building blocks to make more complex data structures.
If you read up on Bliss, one thing that's stated over and over again is that interpretation of
values isn't related to the variable, but to the operators you apply to that variable.
That's one way to deal with types, I guess.
What that means is that instead of Bliss knowing,
say, the difference between adding integers
and floating point numbers,
you have to use special operations for each case.
Bliss comes with an addition operation that can handle full word integer values,
but no concept of what adding a decimal point means.
If you want to do that,
you need to write an operation or function that could treat a variable as a decimal
and handle the actual math itself.
There's no language level enforcement.
The rule of thumb here is just don't write nonsense code.
And yes, we should always strive to produce correct code.
That goes without saying, but the lack of support here feels, well, it feels more than
a little bit concerning.
If we stopped here just with the typeless variable system, then we'd be in quite a strange spot.
Bliss would be a high-level language where you have to do assembly style bean counting to handle data.
What makes this even more strange and perhaps once again wondrous is how Bliss handles pointers.
I have to get a little technical here, so be warned.
Pointers give a high level language a way to talk about where data is stored in memory.
That's crucial for any language like C or Bliss that has to precisely control
what's in memory that goes back to the point about hardware control.
In most languages, an assignment is defined
to be something along the lines of symbol equals expression,
where symbol is some valid variable or pointer
or some named thing.
And expression is any valid code that returns a value.
An assignment will take the value from that expression
and put it in that specified symbol.
It makes an association. Bliss defines an assignment as expression equals expression.
Confused? Concerned? I was both when I saw that definition. The reason this works is that in Bliss,
an unadorned variable is treated as a pointer,
as is the left-hand side of an assignment.
If you see a variable named X,
that's actually a pointer that points to some full word in memory.
You have to do a special operation to get the value that pointer points to.
In other words, when Bliss sees an assignment operation, it evaluates the first expression,
the left-hand side, and treats that result as an address in memory.
Then it evaluates the second expression, the right hand side, and stores its results in
the specified address.
This means you can do wacky and wild things like telling Bliss to store 1 in 1.
That's just 1 equals 1.
Or that x plus 1 equals 2.
That would be valid code.
Now, I ask you, how does that make you feel? I would assume that for many practitioners,
this should feel scary. Maybe it makes you mad. Like I said, this confused me at first.
Why would you write a language like this? Well, dear listener, as I've learned more about Bliss, I think I've started to like
this idea.
But just to be clear, my point of view is a little twisted from exposure to bizarre
programming languages.
By allowing for expressions on the left-hand side, you open up a world of possibilities.
The big one is indexing.
I want to reach for a real world example here.
To do that, I want to introduce another feature of bliss.
To get the value of a variable, you use the contents of operator.
It's just a period.
So you have a variable named X, then X on its own is just a pointer to memory. But dot X is the value that's stored at that address.
On computers like the PC, you have a special region of memory for graphics.
In text mode, this is just a chunk of memory that holds character data.
To print to the screen, all you need to do is store a character somewhere in that region
of memory.
If you want to print a string like hello world, you have to do is store a character somewhere in that region of memory. If you want to print
a string like hello world, you have to use a loop. You start at the beginning of graphics
memory and place the H, then you move to the next address in memory and the next letter
in the word and print E and so on. One way to do this is to set up a pointer at the base
of graphics memory, and then increment
the pointer as you jump through the loop.
That's a very common solution in assembly.
But Bliss offers another way.
Since you can use a left-hand expression, you can write to memory with an offset.
If you're inside a loop, you usually have some value, usually called I by tradition,
that says how many times you've gone through that loop.
You can just say something like memory plus I equals whatever next character you want to print.
I think the actual expression you'd want to use would be graphics memory plus dot I equals string plus dot I that would
Synchronize little indexes and by running that through you just print hello world to the screen
That may sound subtle and pretty technical
The point here is that bliss's strange pointer system gives you more ways to solve problems It gives you different ways to solve problems at It gives you different ways to solve problems. At the
same time, it introduces something funny. That offset trick I described is another commonly
used idiom in assembly language. In fact, the whole expression pointer business is pretty
common in assembly. This is a case where Bliss, once again, is acting like a low-level
language. To be clear, this is a shared quirk amongst all system programming languages. You
have to find some way to balance high-level features and hardware control. Bliss does it with
this strange type and pointer system. You have the ability to use low-level tricks inside a high-level
language. But it's not just doing the same thing as assembly. Bliss is actually a market improvement
here. The offset trick is a good example. In assembly language, you have very limited ways
you can use offsets like that. You're usually restricted to offsets by constants or offset math using
specific registers. You get a little flexibility, but not much. In Bliss, however, you can do any
kind of math you want. You could call a function on the left-hand side. You could do a division
operation or a modulo. In that sense, Bliss brings a whole lot of power
to this lower level practice. Okay, so that's the most basic core of Bliss. From here, it continues
to unfurl. I said that Bliss had a certain orthogonal feel to it. By that I mean all its features work with each other
to build upon each other. So let me unfold the next petal.
Bliss is an expression language. That means that the smallest unit of code is an expression,
as in everything evaluates to some value. That makes sense in most cases.
Two plus two is an expression that evaluates to four.
As we've seen, dot x is an expression that evaluates
to the value of a certain address in memory.
But everything is an expression.
So x equals one is also an expression
that happens to evaluate to the number 1.
In general, Bliss has evaluation rules for everything.
That includes if statements, loops, declarations.
It's everything, every construct in the language.
If you write a line of code, it will return some defined value. This is in contrast to statement-based languages like Fortran or C or Python or...
most other languages out there, actually.
An assignment in Fortran doesn't have a value.
It just tells Fortran to run an assignment.
You can't say x equals if blah.
That would be nonsense.
But in Bliss, an if statement is just an expression.
And an assignment is just an expression, an equal sign, and an expression.
Once again, the language is able to really work well with itself.
So far, this should all sound pretty interesting, if not a little miserable.
As I said, one of the big benefits of a high-level language over assembly is data management.
Everything I've already covered, especially the centrality of the word, puts Bliss just
slightly above the level of assembly.
You have some nice tricks with expression assignment, but not a lot more.
What kicks things up a notch are structures. Now of course, Bliss has to do this its own way.
To get this part to have the proper impact, I have to jump forward a little bit and touch on the
elephant in the room. I've been trying to hold off talking about C, but to explain why Bliss's structures are so strange,
I just gotta do it.
In C, a structure is a way to define a custom data type
composed of multiple values.
Suppose you're writing a program
that deals with data on a grid.
You'd wanna have some way to represent a data type
for a point on that grid. You'd want to have some way to represent a data type for a point on that grid. You
could just make a new structure, name it Point, and say it's composed of two integers named
x and y. You can make new variables of type Point and easily access and set their x and
y values.
When you define a structure, you're also defining its layout and memory.
Our point structure takes up 8 bytes in RAM, 4 bytes for the x value, followed by 4 bytes
for y.
Every new point will take up the same amount of space and have the same layout.
This is one of the ways that C handles precise hardware control.
You can use structures to talk about
memory in a high-level way. C structures are, for lack of a better term, declarative. You declare
you have a new struct named point and you declare what it looks like.
Bliss's structures are usually described as algorithmic, But I think it would be more in line
with the theme of language to call them expressive.
In Bliss, you define a structure
by specifying its access pattern.
You name your struct, a list of arguments,
and then an expression that uses those arguments.
That is, on a fundamental level,
pretty different than how other languages handle structs.
Right away, something like the point struct is out the window.
You don't have named members in Bliss.
The most basic example is an array of words.
For that, you'd make a new structure, call it array, and say that it took one argument.
Let's call the argument i since it's tradition.
The access expression would be something like array plus dot i.
That does the pointer math needed
to get the i-th element of the array.
Then you can make an array and access it
just like in any other language.
Since the access expression can be any expression, you can describe very complex data structures.
You can make multi-dimensional arrays, or trees, or anything.
What's crucial, and what's so different here, is that you aren't defining the shape of
the structure.
Rather, you're defining how you calculate locations within the structure and
how big each element is. You define code, not memory patterns. That's very, very different
than other languages. If I had to go out on a limb, I'd say this reminds me more of assembly
than anything else. When I write assembly, I tend to write either macros
or small helper functions to retrieve or set data.
If I'm working with, say, a list,
I'll write a function to get and set list elements.
That's vaguely similar to the approach Bliss takes,
but not quite the same.
Bliss lets you actually bind the structure,
those access expressions, to variables,
so the language treats those variables in a special way.
Now I think that should do it for the entire data part of Bliss, and let me be clear here,
the data part is really the strange part of the language. Once we get to things like loops
and scope and conditionals,
stuff settles down quite a bit. A big reason for this is that Bliss is based off Algol.
Many languages, including C, are related to Algol at some juncture. It's where we get a lot of modern rules for things like block structure and scope. That's why a lot of languages look vaguely similar.
Bliss is no exception. It uses blocks of code marked by beginning and end exactly like an algal.
Lines end in semicolons, just like an algal. So the general shape of the language is somewhat familiar to the programmer. By 1970, the first version of Bliss was ready for use. Now, the matter of
platform here is important. The first Bliss compiler was developed at Carnegie Mellon for
the PDP-10, made by DEC. In the next few years, it would become a somewhat cross-platform language, as long as you were
on a DEC computer.
In fact, Bliss would become what seems like DEC's favorite language.
For this part of the story, I'm pulling from Roland Brinder's paper, The Bliss Programming
Language, A History.
This is a good paper, but do note if you seek it out that Brenda
writes in the third person in some spots. That, um, that tripped me up for a while,
because Brenda himself was the one that brought Bliss to DEC. It all starts in 1970, when
Brenda met Wolf and was introduced to this strange new language. By that time, Bliss
was already running on Deck hardware.
There was also active work to adapt the language to a new machine, the CEMMP.
This is a whole topic in and of itself, so let me just touch on it and move on.
The CEMMP was a research machine under development at Carnegie Mellon.
It was one of the first big multi-processor
systems and its operating system, called Hydra, was written in Bliss. Bliss was already showing
some promise at CMU. Brenda explains in his history that at that point, the language had
been used for a number of utilities on the PDP-10, and crucially, a compiler. This was supposedly a quote-unquote
what-for-like compiler. I know, vagaries upon vagaries. This takes a second to unravel,
but it's worth it. At this point, Bliss is self-hosting, meaning that its compiler was
actually written in Bliss itself. But remember, Bliss is a bit of a weird language.
It has a pile of idiosyncrasies, and its compiler is somewhat unique and complicated.
So, what is Watt4 and how does it factor in?
Simply put, it was a third-party Fortran compiler written at the University of Waterloo.
Now, I'm going to make an assumption here. It was a third-party Fortran compiler written at the University of Waterloo.
Now, I'm going to make an assumption here.
When Brinder says WOT4 like, I'm assuming he means that this mystery compiler handled
Fortran code.
This is a little tenuous because what made WOT4 special is that it pulled a few tricks
so you could immediately execute a Fortran program after compilation. It could be that Render meant someone wrote
a similar tricky compiler for some other language, but I'm guessing it was for
Fortran because that makes the next step more logical. Either way, by 1970 there
are two compilers written in Bliss. One of those is for some non-Bliss language.
Render learns about the language, gets a demo or two, and becomes a convert. To quote,
As the Bliss compiler stabilized, Render turned his attention towards introducing its use at deck.
Fortunately, a new optimizing Fortran compiler development project, known as Fortran 10,
was starting at a good time to use Bliss 10.
As compiler and language-oriented people themselves, the development team was eager to use a high-level
language for their work, as well as being perhaps less risk-averse than others regarding
the strange new language from CMU. The Fortran 10 group plunged into using Bliss and never looked back."
A note on notation here.
It was common to name DEC software after the version of PDP computer it ran on, so Fortran
10 was for the PDP 10, as was Bliss 10.
Once Bliss moved into DEC, it really became cemented.
According to Brinder, there was some resistance in the larger community of DEC users and employees.
He says it came to a head at a DEC user group conference, DECUS, when attendees started wearing
buttons that said, Bliss is ignorance. That sounds like a great story, and apparently it's true.
I can't find images of said pins, but there are contemporary accounts of this slogan showing
up at user group meetings.
This animosity was eventually tamed once Fortran 10 came out.
It proved to be a capable compiler, which made a lot of detractors retract
their position. Future DEC projects would rely heavily on Bliss all the way up into
the VMS era. So then, why was there so much pushback?
That's one of those things that's hard to quantify. Period arguments get muddied by later speculation. In 1971, as Bliss was making
its debut at DEC, C didn't exist. There wasn't a clear frontrunner in the field. In fact,
there were very few options when it came to high-level system programming languages. As
mentioned way at the top, you had a dialect of Algol, PL1, and maybe BCPL if you were in
England.
The most basic reason for resistance comes down to the classic argument.
None of these languages can be as powerful as assembly.
This is the same argument that John Backus faced way back when he started developing
Fortran.
In that case, programmers didn't want to try a high level language because of concerns
over performance and precision.
If you write an assembly or a machine code,
you have full control over everything the machine does.
With a compiled language,
the compiler decides what the machine does.
You have to give up a little control, which is hard to do.
By 1970, the performance argument had been blown out
the water. A generation of programmers had been raised with high-level languages by that point.
But the precision part of the argument, that was still leaned on specifically in systems
programming. That kind of code does need a high level of precision after all. So there are some valid reasons to argue.
The main critiques I've seen about Bliss come from after C is introduced. I found quite a
few interesting threads from Usenet that discuss issues with Bliss in the 80s, and a few mentions
in some papers and mailing lists. One strangely period argument that I did run into actually comes down to go-to.
Remember how that was a core design goal? Bliss was initially envisioned as a
language without any form of go-to, something to make Dykstra proud.
According to some, that was handled poorly. Listen to this from 1975 in a
paper titled Structured Programming Considered Harmful.
Quote,
It is interesting to see what happened in Bliss, a language whose designers eschewed
the go-to because, in their view, it was redundant.
They were forced, as a consequence, to introduce seven new statements.
End quote.
The paper goes on to list the seven different forms of exit statements used in Bliss.
And this is actually true.
In Bliss, there are seven special expressions for exiting blocks, loops, conditional statements,
functions, and a few other constructs.
It's one of those weird quirks of the language.
You can't just go
to some location outside of your current block. You have to exit. But since Bliss supports
nested blocks, an exit can, in some cases, be ambiguous. Do you want to exit from the
loop you're in, or from the case statement inside of it? As a result, you get a weird set of specialized exit calls.
But that's more nitpicky than anything. The vast majority of arguments against Bliss come
down to its data handling. This even comes from within CMU itself. In 1972, Wolf co-authors
a paper called Reflections on a System Programming Language, which discusses
lessons learned a few years into using Bliss.
One of the critiques is the danger of pointers, especially as implemented in Bliss.
This was a known issue by the 1970s.
Pointers are very powerful, but they introduce whole classes of possible bugs.
If there's nothing to check where a pointer is pointing, then you could end up pointing
back at yourself.
In some languages, that could be avoided, just have the compiler throw an error if you
point into a dangerous direction.
But Bliss lets you do all kinds of weird runtime tricks with pointers.
You can make a pointer on the fly.
What happens when pointer math, which can be very complex in Blis, goes wrong?
Well, we already knew the consequences.
Folk were doing similar tricks with pointers in assembly language.
Blis made the tricks more sophisticated, but the same type of pointer math was common on the lower levels.
When you made a mistake, you'd get a particularly nasty and hard to spot buck.
I know, because I've made those kinds of mistakes.
Most recently, I wrote some bad code that accidentally pointed to a return stack.
It took me at least a day to figure out what was wrong.
Bliss's pointers are very powerful, but they can also be very unstable.
There's also the matter of type. This is one of the largest arguments against Bliss in more
modern discourse. Typing makes a language safer. What do I mean by that? By enforcing data types, you prevent a lot of accidental and just stupid bugs.
In a more strongly typed language, you can specify that a function must be given a number,
for instance.
That prevents you from, say, trying to add a number and a character and getting unexpected
results. Or perhaps trying to do some pointer math and accidentally adding two irrelevant values,
resulting in some nonsense pointer directed out into memory space.
There's one other big issue that comes up in any critique of Bliss.
That is, portability.
One early design goal was machine independence.
Now in theory, Bliss as a language
could have been portable. There's nothing very machine dependent about the language
as such, or so it would seem. There are some specific details about its data type, the
full word, that cause issues. Bliss 10, the first compiler, used a 36-bit word.
That meant code written for Bliss 10 assumed that each variable would be 36 bits wide.
The next compiler, Bliss 11, was written for the PDP 11.
That machine had a 16-bit word.
So a full word in Bliss 11 is 16 bits instead of 36. There were also changes in
how pointers were handled because the PDP 11 handled addresses differently than the
PDP 10. The result is that code written for Bliss 11 isn't necessarily compatible with
Bliss 10 and vice versa. Eventually, DEC codified a so-called common bliss, a standardized dialect that was fully
portable.
But that didn't happen until the latter part of the 1970s.
And it was only really used within DEC.
The language was kind of trapped.
Then we get to the real elephant in the room.
In 1972, a language called C is developed inside Bell Labs. In 1973,
Unix is rewritten in that language. In 1976, Lion's commentary on Unix is published, which
is used extensively in university classrooms. The book uses Unix's source code as a case study in
programming, carrying C along with it. I personally think
that once the commentaries are published, it's a little too late for Bliss. UNIX spreads quickly
after that point, as does C. The spread of the language is, of course, much more complicated
than that, but I think that's a reasonable summary. The C programming language was developed
with many of the same design goals as Bliss. It was meant to be a systems programming language.
It was designed specifically to be used in Unix, an operating system. C has pointers,
but they're slightly safer than the full throttle pointers used by Bliss. Both languages are inspired by algal,
so the syntax is even similar.
The big difference,
what nearly every argument boils down to, is typing.
C has variable types.
Bliss does not.
That means that C is, right off the bat,
a safer language.
At least somewhat.
It's still kinda dangerous with how it handles pointers, but, you know, potato potato.
On a technical level, C is less error-prone than Bliss, but it's able to do most of the
same things.
On its own, that doesn't necessarily lead to a winning language.
The final factor is availability.
The spread of Unix is tied to the spread of C. You can't get one without the other.
As more and more mini computers start using Unix, that means more and more users have
access to C. The C compiler comes along with every version of Unix.
And since Unix was initially distributed as source code, every Unix installation came
with a pile of C code to look at.
By entering Unix, you were entering the world of C.
Bliss, on the other hand, never had that kind of availability.
DEC used a lot of Bliss, but the Bliss compiler was never generally available to the outside
world in the same way as the C compiler.
That paired with its shortcomings made it a much weaker showing than C. I just don't think there
is ever any getting around that.
Alright, that does it for today's episode. We've seen how Bliss emerged, what
made it unique, and what made C better? Does this mean that Bliss is a bad language?
Is Bliss truly ignorance?
I'm tempted to say yes, but I've learned better.
My saga with track has taught me to judge languages a little less harshly.
Bliss is a very unique language, especially for a high level language.
It represents the snapshot in time when we didn't have very good
tools for high-level system programming. Sure, there are parts of its design that are flaky,
dangerous, and perhaps annoying. However, there are reasons for those choices. It's not just an
amateurish system, it's not just something to point at. It was built to solve a very specific
problem, and good choices were made
for a lot of its design. C isn't just a better language than Bliss, it's not that simple.
There are many features of C that are better than equivalent features in Bliss. Take the entire data
handling parts of these languages. The complication here is that Bliss may be better suited for representing some types
of code.
You can just solve problems in different ways in Bliss because its variable system is so
different.
I'm also saying this because Bliss has kind of charmed me.
It's an interesting language that I'd actually like to mess around with.
To that end, there are modern compilers. Notably, OpenVMS comes with
a Bliss compiler, which you can actually get hobbyist license for now. That program was
cancelled for a while, but it's back, so if you want to play with OpenVMS as a hobbyist,
you can do that for free. FreeVMS also has a Bliss compiler, from what I've read.
There's also the Bliss-im compiler, which is an open source project.
I've been playing around with that one myself.
You can compile Bliss M on Linux,
so it's easy enough to use.
Right now I'm trying to link some Bliss
to an entry point written in C,
so I can try to boot a computer into Bliss.
Gonna see if that actually goes anywhere,
maybe a fun weekend.
The point is, if you want to take the plunge, there are tools and resources out there.
Thank you so much for listening to Admin of Computing.
I'll be back in two weeks' time with another piece of Computing's past.
If you like the show, there are a few ways you can support it.
You can tell your friends.
Word of Mouth is a great way to help grow the show show You can also rate and review the show on Apple podcasts and Spotify
You can support the show directly by signing up as a patron on patreon or buying merch from my merch store
Links to all of that are on my website admin of computing comm
Right now on the patreon as I mentioned in the top. I'm in the process of picking the next bonus episode
That process is going to go on for a few more weeks, so if you want to get in on that, please
go over to my website, go to the Patreon and sign up.
It costs as little as a dollar a month.
And as always, have a great rest of your day.