20 some-odd years ago, when I was a graduate student, I spent about two years building Mac applications using a language called Prograph.
You’ve likely never heard of it.
I want to explain why I’m still kind of obsessed with it.
I’ve spent a lot of the intervening 20 years explaining to people why it was great. I’ve I’ve been capable of delivering this as a lightning talk at the drop of a hat at any time in the past ten years.
Prograph was a purely visual language — they called it a data flow language. And by visual, I mean visual. Prograph code looked like a flow chart.
I’ll get to the mechanics of the language in moment, but I want to lead with what it felt like to work in a visual language.
I felt great.
Python has a reputation of being executable pseudo code. Prograph felt like an executable flow chart.
I found it extremely intuitive, and much less prone to syntax problems that textual languages. The development environment made what might otherwise be a difficult task easy and game like. It had the best runtime debug environment I’ve ever used. It also had one of the best interface builders I’ve ever used.
Prograph is hard to write about these days. There isn’t a reliable IDE that can run it anymore and it was long enough ago that there aren’t many tracks on the internet. I’m not 100 percent sure what the source of these screen shots is, I think it’s a tutorial from MacWorld in the mid 90s, but the images have vanished off the Internet, I pulled them for a talk a while back.
Here’s how Prograph worked.
An editor window for a new function or method in Prograph consisted of a blue bar at the top of the pane and another one at the bottom. Clicking on the top bar createed a circle which represents an input value, clicking on the bottom createed a circle representing an output value. They aren’t named, as such, but could be freely given labels.
Coding in Prograph involved drawing lines between the outputs of one function to the inputs of another.
Here’s a “Hello world”.
This function takes no inputs and no outputs. The string literal “Hello, World” is sent as input to the library method “show”, which displays a dialog.
The developer environment provided a menu listing all the types of operations that you could insert into your function, with “constant”, and “built-in primitive” being two of the options. You’d add them to the screen, drag the connection between them, and move them around as you saw fit.
Here’s a slightly more elaborate example:
The dotted background means this screenshot was taken in debug mode. Again, no inputs to the method.
The octagon shape indicates instance creation, in this case creating an instance of the class “Text File”, which was (I think) provided by the system — note that you can have spaces in identifiers here because the language doesn’t need to parse the text.
If I’m remembering this correctly, the new instance is passed to a method called “/Get File”, the leading slash means it’s an instance method called on whatever is the leftmost argument to the method (evaluated dynamically at runtime, Prograph was a late-binding duck-typed language, with similar semantics to Python). The second argument here, I think, is a list of old Mac System file types. The output of “/Get File” is passed to “/Open”, another instance method that opens the file, and the opened file is passed to “/Read Line”, which reads one line of the file. That line is passed to a system method — indicated by the think second line at the bottom of the rectangle — called “in”, which takes a second argument and returns true if the first argument contains the second.
By now you’ve probably learned three things:
- The visual nature of Prograph makes some kinds of syntax errors basically impossible
- The visual flowchart syntax is very love-it-or-hate-it. Some people flip, others have a hard time making it out. (I will say that allowing spaces in method and variable names had a surprisingly high effect on readability).
- It’s very hard to discuss Prograph code in words.
A feature that’s a little less obvious is that, unlike a textual language, the order in which commands happen depends only on the data flow. In the above example, commands happen heading down the screen, but only because each command is dependent for its input on the output of the previous commands. The two literal operations can happen in any order that will allow them to be executed before their dependencies. (I don’t think the 1996 version literally ran in parallel, but the language was certainly set up to encourage parallel thinking.) Here’s a different example that shows three different sorts that could happen in any order.
The weird shape is an object attribute getter. Some language semantics were handled by using different shapes other than rectangles for the callable things. For an object getter the input is the object, left output is the same object as the input, and right output is the value. Object setters had a different custom shape, as did global variables.
Other language semantics were handled by annotations to either the input or output of a function. You could turn any input dot into an ellipsis and Prograph would then expect the inflow to that dot to be a list, it would then automatically run the function once for each element in the list — there’s your
each. You can similarly make one of the outputs an ellipsis, and Prograph will gather the result of each run through the function into a list output — there’s your
map. There was an annotation that would take an output and wind it back to the top to become an input on the next loop — there’s your
inject. Other enumerable type methods were pretty easy to build on top of those constructs.
There was also a special annotation called an injection — you would leave the name of the function rectangle blank and pass a string name to the rectangle using the injection input. The function with that string name would be evaluated. This gave you some decent metaprogramming capabilities.
(As I look at this I think that the unneeded crossing of lines here would have been considered bad style, and that the preferred style would have been to keep the list as the leftmost argument to the method being injected into).
Conditional if/then logic was kind of a pain. A function could have multiple clauses, and essentially the language construct provided was “move to the next clause if this boolean condition is true”. Any such tests run before the any other code in the clause, so effectively you have a bunch of guards determining whether a given block of code is run. This was far and away the most confusing construct in the language, it took a while to get it right, it was never super-clear, and it involved a little bit of re-thinking code that depended heavily on conditionals. (I think it had a special case ternary operator for simple conditionals, though. If it didn’t, it should have).
Prograph also had an interface builder along the lines of Visual Basic. You could draw windows with widgets, and assign events to execute code. The code could compile to a regular Mac executable that you could distribute.
The real best part, though, was the developer runtime environment. The developer environment was interpreted. If you were running code and you went to call a function or method that did not exist, the environment would pause, ask you if you wanted to start writing the function, then open a window for you. When you were done, it would pick up execution and run the method you had just written.
In debug mode, Prograph really took advantage of being a data-flow, basically no side-effect language. You could step through execution, but at any time you could see the data at any point of execution (basically you could mouse over any data circle you could see) even if execution had moved far beyond. So you effectively got a watch on all variables at all times for free. You could also go back in time, undo execution steps, edit the function being called, then redo them with the new code without exiting the runtime. Here’s a screenshot showing the mouse over of a previous bit of data even though the code is later in the flow.
So, while there wasn’t really a REPL, the runtime environment was pretty powerful. I don’t exactly remember if you could enter the runtime at arbitrary points or whether you had to execute the program from the beginning. I do remember that developing in it was very nice.
The overall effect was a language that encouraged a lot of good coding practices: small methods, few conditionals, breaking loops into their own methods, good naming practices with a powerful runtime that made editing and debugging working code pretty easy.
Elegant algorithms were very satisfyingly pretty, spaghetti code looked like actual spaghetti.
I loved it.
So what happened to this amazing coding environment?
They had two sets of problems. One was that it was a relatively small user base and it was very hard to share non-textual code and discuss code over the internet circa 1996. I think somebody would have eventually figured this out, but it was a real limitation, and made it somewhat hard to have shared libraries and code repositories. There wasn’t really a good solution for non-textual change management.
More importantly, they were a Mac-only boutique commercial programming language in 1996, and reality was about to hit them hard. With their Mac user base dwindling, they tried to convert to a Windows version, but they could never get it to work well, nobody on Windows cared about them anyway, and their Mac version stagnated. Then, open source languages happened in a big way and all kinds of commercial programming tool vendors got slammed. They tried to refocus as a ColdFusion-style app server, but as far as I can tell it went nowhere. There was an attempt to create an open source implementation that cratered pretty fast, I don’t think it ever released anything, and there was an attempt at a commercial Mac OS X version that seems to have petered out about five years ago. You may not be surprised to learn that there was some legal and licensing related wrangling between the original owners, the open source team, and the new Mac team, the details of which I don’t really know, but which certainly didn’t help anything get released.
Which is a shame, it was a unique and fun programming tool, there were some good ideas in it that I’ve never seen executed quite as well, and if I had an infinite amount of time and money, it could probably be the basis of a decent browser-based framework or something.