The seven programming ur-languages (2021)
madhadron.com
madhadron.com
The two earliest macro systems that were clearly designed to be general purpose languages that I know of are Christopher Strachey's GPM[2] and Calvin Mooers TRAC[3] programming language. These languages appeared at roughly the same time, the mid 1960s. I prefer the syntax of TRAC, but otherwise they are almost isomorphic. TRAC was featured in Computer Lib/Dream Machines[4] by Ted Nelson where the author said it was one of the three important languages for programmers to learn. A good introduction to TRAC and it's implementation can be found in Études for Programmers[5].
Other more contemporary examples of macro programming languages are m4, and TeX. LaTeX is programmed in the TeX macro system.
[1] Daniel Weise and Roger Crew, "Programable Syntax Macros", ACM SIGPLAN, 1993, https://dl.acm.org/doi/pdf/10.1145/173262.155105
[2] Christopher Strachey, “A general purpose macrogenerator,” Computer Journal, 8(3), pp. 225-241, 1965
[3] Calvin Mooers, "TRAC, a procedure-describing language for the reactive typewriter", CACM, Vol 9(3), March 1966, pp. 215-219, https://dl.acm.org/doi/10.1145/365230.365270
[4] Ted Nelson, "Computer Lib/Dream Machines", 1974, Self-published. (There is a 2nd edition from Microsoft Press, but I'm only familiar with the 1st edition).
[5] Charles Wetherell, "Études for Programmers", 1978, Prentice Hall. (It's out of print and available from Amazon for $427. I'm going to have to start locking up my old books.)
At least that's the viewpoint I'm familiar with from back when we heavily used it.
Here's a good explanation.
It's a way of structuring an interpreter. In forth a word is just a pointer to its definition, which in turn is just a list of pointers to other words, potentially user defined or built in primitives. Execution threads through these pointers, much like making subroutine calls with arguments being passed implicitly on the stack.
It's much more compact and lower overhead than a classic interpreter walking a full Abstract Syntax Tree structure. These days most languages are going to a full native code JIT however.
Thanks for the tip on Études for Programmers, looks like I'll be able to look at it in a library at least, surprised archive.org didn't have it.
I would maybe add SQL as an ur-language as well. It's not quite general purpose like most of these, but it should have a place in this list, I think. It has some kinship with Prolog and the declarative style, but it's really it's own thing.
You could also maybe argue for something like LabView. Many programmers look down on purely graphical programming languages, but with Houdini, Unreal's Blueprints and the various node-based shader/material systems in gamedev, I think it probably deserves it's own little branch of this family tree.
Programming PLCs sometimes feels like going to, say, Australia or New Zealand, and being immersed in an environment which went down a different evolutionary tree very on in its development.
For the signal transformation ones, there are also the hardware definition languages on the same category as LabView and animation languages.
I can't really attest to whether that's a better choice for learning logic programming, though. It's easier to "run programs" in Prolog. SQL wants to be a cog in a machine where Prolog is more freestanding.
I would absolutely love to take a set of logic/set-oriented problems and solve them both in Prolog and SQL, just to see which ones are ergonomic in each language. Maybe this summer at the cabin...
https://technology.amis.nl/it/solving-a-sudoku-with-one-sql-...
SQL and Prolog are both relational. That's very unique to both. But SQL is all about querying databases. Prolog can also be used and understood as a database querying language but it's also very strong for
- parsers
- interpreters
- expert systems
- solving combinatorial problems
If you really want to you can probably use SQL for that too. Or any language for that matter. But going out and learning SQL won't naturally expose you to these applications and how well the logic paradigm lends itself to them.
Recursive common table expressions are part of the SQL standard (since 1999) and are quite frequently used to traverse hierarchical data (aka "adjacency list").
It is part of basically all (good) SQL tutorials - at least in the "advanced" part.
WITH RECURSIVE thread(id, parent_id, user_id, post_id, timestamp, text, depth) AS (
SELECT id, parent_id, user_id, post_id, timestamp, text, 0
FROM comments
WHERE user_id = 1
AND parent_id IS NULL
UNION ALL
SELECT c.id, c.parent_id, c.user_id, c.post_id, c.timestamp, c.text, t.depth + 1
FROM comments c
JOIN thread t ON c.parent_id = t.id
WHERE c.user_id != t.user_id
)
SELECT * FROM thread ORDER BY timestamp ASC;They do come up!
But lacking those general features makes it an even purer "learning example" of the group.
And, not to put too fine a point on it, being extremely proficient will give you a massive competitive edge in the industry.
This is one of those things that seems to keep coming back - recently in the Ruby world with Cucumber.
COMPUTER, SELECT COURSE WHERE MINIMAL PROBABILITY OF KLINGON ENCOUNTERThere have been very few non-English programming languages. There was a French version of COBOL once. I'm surprised that something hasn't come out of China. There's a Chinese dialect of Python. Does anyone use that?
Way to age yourself old man.
Learning erlang made me definitely at least 10% smarter as a programmer. Highly recommend, even if you never use it in anger.
And not quite sure if SQL could be grouped together with Prolog due to its declarative nature. But if so, DBMS is, like OTP, quite far from Prolog, just in a slightly different direction :)
I guess what I'm trying to say is that the list of ur-languages seems quite fine to me.
> A Smalltalk object can do exactly three things:
> 1. Hold state (references to other objects).
> 2. Receive a message from itself or another object.
> 3. In the course of processing a message, send messages to itself or another object.
Replace "Smalltalk object" here with "Erlang process" and the description holds.
> Unlike most other languages, Smalltalk objects can be modified while the system is running. Live coding and applying fixes ‘on-the-fly’ is a dominant programming methodology for Smalltalk and is one of the main reasons for its efficiency.
Erlang's famed robustness similarly owes much to its ability to update code in a running program.
[1] https://en.wikipedia.org/wiki/Smalltalk#Object-oriented_prog...
(I categorized the temporal declarative language TLA+ into the Prolog family in another comment -- a main distinction here is, though one can express reactive relationships in TLA+, the intent of the language and design of the TLC model checker is that such relationships are only usable for verification code, whereas implementation code must be written as state-succession pairs. A true reactive language permits and encourages both styles of coding for implementation code -- e.g. Verilog's = and <= operators. Vice-versa, a true logic language includes nondeterminism as a core construct, whereas reactive languages typically do not.)
[1] https://en.wikipedia.org/wiki/Synchronous_programming_langua...
From a PLT perspective they are programming languages just like any other, and one can analyse them using the same machinery (formal semantics, typically of the operational style) as any other programming language.
Just because they are not even close to an imperative paradigm doesn’t make them any less of a programming language. They are reasonably close to process-calculus/message-passing languages in the sense that you can treat a Verilog process as a receiver listening to channels that contain messages generated when nets/variables are driven.
I agree that a software developer who tries to write Verilog as if it were C will very quickly run into trouble, but that isn’t because it’s not a programming language; it’s because it’s a programming language with a similar syntax to C but with drastically different semantics.
(Yes, it's easy to accidentally write unsynthesizable code in them -- but similar issues hold true for many declarative languages.)
The parallelism in hardware is indeed extreme. Like if every line in your program ran at once, and then repeating every clock cycle.
IMHO* a distinguishing feature of Algol like languages (aka "procedural" languages) is the distinction between expressions and statements. Though personally I've never seen the appeal, that distinction seems to be popular for some reason.
* this isn't even a quibble -- the article is fine without it. Just something that has always seemed weird to me.
But if you change your definitions such that expressions always evaluate to a single value and do not have any side effects while statements produce some kind of side effect (and may or may not yield a value), the distinction becomes important.
Especially if you believe side-effects need special handling (i.e. you are a functional programmer).
Are there any good examples of languages that have it, where it isn't "useless"?
But I think the argument being advanced is that the distinction between statements and expressions is fundamentally unnecessary. I don’t really know of any good argument in favour of them: I tend to think lisps have the perfect and simplest possible syntax.
C is less particular about the expression/statement distinction. Java enforces redundancy.
(to be fair, it took a lot of inspiration from Smalltalk and LISPs)
Listing Self rather than Smalltalk as the basis of OO languages is also a bit odd. Calling it a "purer form" I guess is a justification, but the other view is once you losen the definition as above, self is just the root of one small (albeit influential, via Javascript, branch of OO languages that almost all owe more to Smalltalk than Self. If another language than Smalltalk should be at the root, it should be Simula, for inspiring message passing, not Self.
I don't agree it made the concepts more OO in any sense. Self's changes feel far less significant than what Smalltalk brought to the table. That an object inherits it's structure and functionality from something that is itself an object is true of all Smalltalk derived languages I'm aware of, after all.
Since we can get from Self's model to Smalltalk's model by adding restrictions on what you can do to both objects and prototypes, Self is quite objectively a better type specimen. I wouldn't call it an "ur language", that was a silly choice in term, but as basis for discussing OOP models, it's the most permissive, and you can get the other OOP models by adding various (sets of) restrictions.
(Much like how a regular grammar is a context free grammar with additional rules)
The defining aspect of OO languages if we go by Kay is message passing and late binding. How that is achieved is secondary to the classification. Both Self and Smalltalk provides that, but Smalltalks way of providing that is more typical of the class of languages as a whole.
To me, either Kay is authoritative, or we go the other direction and include more of the ALGOL-derived languages. In neither case is Self a good representative of what OO languages are like, and that to me makes it a poor type specimen.
So yeah, we'll have to disagree.
This is ironically exacerbated when what is often presented as the important bit of Smalltalk are examples like the one in the article of allowing definition of control structures (you can do that in Ruby too, e.g. a partial impl: "def true.ifTrue = yield" - now you can do "(1 < 5).ifTrue { ... called if true }" - with a bit more thought you can chain ifTrue/ifFalse).
But while that's neat, that doesn't even require dynamic dispatch, just the combination of being able to invoke methods/dispatch messages to true and false, combined with convenient syntax and support for closures. You could add that to a language and still not have a Smalltalk descendant in any meaningful way.
What matters much more is that objects at least may take control over the target of a message dispatch even if they often don't, whereas method invocations in "those other" OO languages is mainly controlled by the class definition, and that is often glossed over as an advanced subject or "scary magic". For example how Ruby ORMs tend to use introspection to let you dynamically treat columns on database tables as methods based on the actual current schema of the database - in other words the ability to do not just dynamic dispatch but late binding.
One weird aspect of Smalltalk that more or less directly comes from the control structures implemented as messages taking blocks is that on the language level there are two distinct function-like objects: methods and blocks (ie. lambdas) that behave differently and interact with each other (return statement is scoped to method and only valid during the dynamic extent of said method invocation).
The same way you can override "method_missing" in Ruby but you apply it as rarely as possible, but the dispatch is still dynamic and methods can be overridden and dynamically defined. That's the point. Not that you literally implement a dispatch directly on the object.
Put another way, the main distinction is that the precise method body invoked by sending a given message to an object may be impossible to statically determine.
> One weird aspect of Smalltalk that more or less directly comes from the control structures implemented as messages taking blocks is that on the language level there are two distinct function-like objects: methods and blocks (ie. lambdas) that behave differently and interact with each other (return statement is scoped to method and only valid during the dynamic extent of said method invocation).
Ruby sort-of inherits this too, but at any point where you take the value of a block, it becomes an object - it's purely an implementation artefact, and with lambda/proc providing both lexical and method-local scope for return.
when learning smalltalk i could not see the what was so special about message passing, and why it is even called that.
consequently, i find the distinction between message passing vs function or method calling academic. more interesting are distinctions like static vs dynamic dispatch and early vs late binding and whether things are defined at compile time or runtime.
the term "message passing" always made me feel like this should be something completely different and not even remotely similar to function calling. yet i couldn't see that difference and that left me irritated because i felt like i was missing something.
E.g. my long-languishing partial Ruby compiler uses C++ style vtables because they're fast and the "only" challenge is that you need to propagate method re-definitions down a chain of descendant classes (and avoid overwriting overridden versions in the descendants). In practical terms, pseudo-code for a method dispatch is ob->class_ptr.vtable[method_slot](args...) and all of the dynamism happens with a combination of dynamically overwriting and propagating method pointers down the vtable chain (I was worried it'd lead to way too much memory spent on sparse vtables and having to fall back on a hash table for less-used method names, but in practice the number of classes is usually very constrained) and filling in thunks that forwards to method_missing for names that are seen in the system but not implemented by the current class.
Effectively you can consider the vtables as perfect pre-filled caches. To someone casually looking at them to get an idea of Ruby, it'll look like Ruby's object model is almost the same as C++'s.
So I agree with you that the key practical difference is static vs. dynamic dispatch and early vs. late binding. With those distinctions you don't really even need to make the compile vs. runtime distinction. That is, you can statically compile something that includes dynamic calls to modify the object model, like my prototype compiler.
Elsewhere I suggested one way of looking at it is that to distinguish the C++/Java etc. and Smalltalk model of OO, the key test is how common it is for there to be code where determining which method body will be invoked by a given call devolves to the halting problem if you don't have the precise inputs ahead of time (e.g. in Ruby, almost every ORM would cause this).
That was my first reaction too. I also agree that Simula deserves a mention here. AFAICT more languages derived from that model than from Smalltalk or Self directly, and I'm pretty sure the authors of later languages such as C++ have acknowledged as much.
The reason I'd be ok with Smalltalk there is that Smalltalk at least was a significant break and you could argue that many of the "non-Smalltalk-y" OO languages are really ALGOL-derived languages that took some OO aspects (especially if you move Ruby out of the ALGOL bucket) in that most of them tend to combine support for both non-object types and objects, and have lots of constructs that operate on non-object values.
As such I can see the merits in both/either of Smalltalk and Simula treated as the "ur-language" for OO in a way I can't for Self.
Self is remarkable, but as I've mentioned before, more for the advances its implementation brought.
If you accept that narrow definition at least for the scope of the article, it makes sense.
E.g. in Ruby you can not take the value of anything and get anything but an object (e.g. integers are objects, true is an object, nil is an object), but Ruby is not an OO language by the article's definition because it fails the part about conditionals.
Even though you can do this in Ruby (probably buggy, just threw it together) - it's just not idiomatic and the language has syntactic sugar for "less OO" forms:
def true.ifTrue; yield; true; end
def true.ifFalse; true; end
def false.ifFalse; yield; true; end
def false.ifTrue; false; end
(1 < 5).ifTrue { puts "TRUE!" }.ifFalse { puts "FALSE!" }
# Or:
(1 > 5).ifTrue { puts "TRUE!" }.ifFalse { puts "FALSE!" }
So it might make sense if you accept that narrow definition, but I don't think many people will find that narrow definition to make sense. I certainly don't.If I were to put Ruby into one of these categories, I would place it first under Self (the object-oriented languages). Ruby is the most object-oriented language that I've ever used in that everything is an object that you send signals to. Even classes in Ruby are objects (they are instances of the `Class` class). Ruby was explicitly inspired by Smalltalk, one of the two exemplars cited by the post.
After the "Self" category, Ruby would fit better under the "Lisp" family than the "ALGOL" family because of Ruby's deep metaprogrammability.
I'm guessing the author was fooled by the availability of C-style `for` loops in Ruby, but that's generally not how Ruby programmers write a loop. It's much more common to write `list_of_things.map {…}` or use any number of other iteration methods available through the `Enumerable` module.
JavaScript to me fits better under the ML (functional languages) family than "ALGOL". The first-class nature of JavaScript functions is the core feature of the language. Of course, if you define "functional languages" by having static type systems this grouping wouldn't work for you. But for me it's all about the functions. You can pass functions around and return them from other functions. You can write utility functions to memoize or otherwise transform functions.
And while not everything in JavaScript is an object the non-object values in JavaScript have "object" versions. JavaScript still has some object-oriented chops. Functions are themselves objects, and while method calls are usually just reading a function off of an object and calling it, you can intervene in the property-reading step to enforce a more "message passing" style.
I'm guessing that most developers will have similar quibbles about the categorization in this article of the languages they are most familiar with. But this is still an interesting frame of reference. And if you only work in languages that fit squarely in the "imperative" category (or write code in an imperative way), I encourage you to explore some of the others.
> After the "Self" category, Ruby would fit better under the "Lisp" family than the "ALGOL" family because of Ruby's deep metaprogrammability.
I don't agree with this: Lisp's metaprogramming capabilities come from its macro system, while Ruby's metaprogramming capabilities are due to metaclasses (which is exactly why it fits in the Smalltalk category).
JavaScript is also not a ML-style language in my opinion. ML-like languages have not just types, but also pattern matching and algebraic data types. First-class functions is not unique to the ML category, it also applies to the Lisp and Smalltalk categories (and maybe APL, I don't know it enough).
I also still think of JavaScript as an ALGOL-style language augmented with Self concepts (Self, not Smalltalk!), but this may be because when I first started with JavaScript it didn't have classes yet. The good old days...
When they added the `class` keyword in JavaScript it didn't change the capabilities of the language—it's still prototypal under the hood, but I guess the syntax matters. I certainly see people writing "classes" in JS a lot more now.
Though, see the Common Lisp Object System Meta Object Protocol (CLOS MOP). There were a bunch of meta-object systems for Lisp, with the CLOS MOP as the most prominent example.
That's fine, this isn't actually a taxonomy, these ur-languages are the progenitors of concepts and techniques. Of course they can blend in decedents.
Not everything, there are still a few keywords that are not objects. For example `end.class` will raise a syntax error.
I second this. I would consider myself an intermediate level programmer and learning Scheme (via the excellent book "Structure and Interpretation of Computer Programs") took my programming to a new level and made me think of programming from a completely different angle.
Algol -> C. Mostly because you can actually do things with C, and yet it remains a fairly small language that's a relatively pure exemplar of the Algol tradition.
Lisp -> Scheme. Also because it's a tiny language that tries to push the fundamentals of the Lisp family (code-as-data, recursion, functional programming, macros) as far as possible.
ML -> Haskell. ML is eager, Haskell is lazy. If you're going to learn about the ML family, you might as well learn the concept of lazyness, which results in a very different style of programming than FP of the Scheme variety (in the Lisp family).
APL -> J. The usage of special symbols is largely irrelevant to the concepts in APL, and it's a barrier to accessibility. You can learn all the important parts of array-oriented programming with J and use actual words to do it.
Self, Forth, and Prolog I would keep as exemplars of their type. I would also add TCL as another ur-language for string-based scripting languages (with Perl, PHP, and SNOBOL as other representatives of the category).
For a famous example, Umberto Eco's essay "The Ur-Fascist" isn't describing what the first or original fascist movement was, but instead exploring what makes Fascism Fascism. This usage seems to be pretty consistent across academia, who engage in the vast majority of cases of slapping foreign prefixes onto English words.
> You can learn all the important parts of array-oriented programming with J and use actual words to do it.
J doesn't use actual words. It uses symbols just like APL. In fact, APL uses proper words or abbreviations for utilities things that are not part of the core language, whereas J just uses numeric codes combined with more glyphs. However, J is ASCII-only (and uses bi- and even tri-glyphs) where APL uses pleasant and mnemonic Unicode single glyphs, so you don't need to parse which adjacent ASCII symbols form a "word".
avg =: +/%#
&.> is predefined as 'each'.
Same for APL, but the idea is to learn what the symbols mean, so you can keep it concise and think in composing functions and not reading pages of text. Sort of like mathematicians and mathematical symbols.
I program in J and APL, but I have taken to BQN lately. It is the best of both with some additions too.
I would say the same about Lisp.
In fact, each list in Lisp is like its own little stack in Forth. Prepending an element to the front of a list is like pushing onto a stack. Separating the first element from the rest of a list is like popping from a stack.
Lists of lists & atoms are like stacks that can contain references to other stacks, as well as atoms.
> Besides, the determined Real Programmer can write fortran programs in any language.[1] https://www.softwarepreservation.org/projects/FORTRAN/paper/...
* Shader code: is ostensibly C, but fundamentally different runtime characteristics.
* Dataflow (or Signals/Excel): Topological graph of computation.
* HCL (or Cloudformation/CDK/etc): Connecting execution via a different runtime.
* React Hooks
IDK it just seems like there is a lot.
And then there’s basic…. Maybe that is just pidgin, giving those who could not speak at all some words?
[1] https://craftofcoding.wordpress.com/2021/01/11/why-algol-bes...
I disagree with this line since Lisp is not really a "language", but a family of them. If we consider Clojure and original Lisp to be the same language, we should also consider that to be true of Algol and Rust.
> I am aware of seven ur-languages in software today. I’ll name them for a type specimen, the way a species in paleontology is named for a particular fossil that defines it and then other fossils are compared to the type specimen to determine their identity
The Strongtalk technology also came from Self; it was thought that Self would be too hard to make fast, until it wasn't.
As for total beginners, it can be demeaning to explain why asking which language of the ALGOL descendants to learn is a pointless question. For their case, I don't bury the lead: I tell them "They all have the same lineage and are fundamentally similar, so just pick the one you'll learn the most robustly with. Most everything else is learning libraries."
- value semantics with both implicitly-copyable and move-only values
- unboxed generics with type classes/traits/protocols and associated types
- as little syntax and sugar as possible for everything else
Yes, that's what I meant here.
With code example:
https://github.com/coq-community/coq-100-theorems/blob/maste...
Smalltalk is a branch, but it's an important enough branch introducing important enough new concepts that unlike with Self I wouldn't have an issue with people considering Smalltalk it's own ur-language, and because I agree with you that Simula at least on the surface will seem more familiar to people familiar with ALGOL-derived languages with OO mechanisms than to Smalltalk.
Self, on the other hand, is less important for its concepts (ok, so it has prototypes instead of classes, but classes in Smalltalk are also objects, so I don't buy that it's that conceptually different, especially in a dynamic language where you can dynamically instantiate and mutate classes) than it is for the papers on its implementation.
What do you have in mind, apart from duck typing?
> considering Smalltalk it's own ur-language, and because I agree with you that Simula at least on the surface will seem more familiar to people familiar with ALGOL-derived
For ST we have to differentiate the 72 and 74 from the 76 and later versions. Starting from 76 it has inheritance, compiled methods and virtual method dispatch quite similar to (though less efficient than) Simula 67.
The focus on message passing and late binding combined. "Duck typing" is seriously diminishing it. You can write code that appears that way even in C++ with RTTI and inheriting from a shared root class and heavy use of virtual. But to achieve the equivalent of the combination of message passing and late binding in a language like C++ not built for it you typically end up having to build your own message dispatch machinery with no syntactic support to make it cleaner that will make your code look fundamentally un-idiomatic, and so doing so is tends to be limited to specific problems.
It's this combination that makes Smalltalk-derived languages feel different.
> For ST we have to differentiate the 72 and 74 from the 76 and later versions. Starting from 76 it has inheritance, compiled methods and virtual method dispatch quite similar to (though less efficient than) Simula 67.
I'd argue when we say Smalltalk without qualifying it, most of us will be talking about Smalltalk-80.
Actually even ST-72 made synchronous calls, but at least with a token stream interpreted by the receiving object (thus at least a bit of "message passing"). In ST-76 and later versions "message passing" is just nomenclature used by the ST folks for something that is just ordinary method dispatch and call (if you have doubts, you can analyze the innards of the ST-80 VM yourself e.g. with these tools: https://github.com/rochus-keller/Smalltalk ). The major difference is the dispatch based on signature hash (similar to e.g. Java interface method calls) instead of static offsets, which enables late binding (at the expense of performance); and since everything including ordinary integers derive from Object, all values and objects are subject to dynamic method dispatch; it's no coincidence that Smalltalk was the first language to be associated with duck typing. The unification of scalar values and references, dynamic typing, and likewise the minimal syntax where control structures are implemented by means of runtime constructs were already known from Lisp; also closures (i.e. ST blocks) were already known before they were added to ST.
I've not suggested it is anything but synchronous, so I don't know why you're bringing that up. It's not what we're talking about when we talk about "message passing" in this context.
> In ST-76 and later versions "message passing" is just nomenclature used by the ST folks for something that is just ordinary method dispatch and call (if you have doubts, you can analyze the innards of the ST-80 VM yourself e.g. with these tools: https://github.com/rochus-keller/Smalltalk ).
Sure, you can implement method dispatch the same way. I've written a (partial; unfinished; very buggy) Ruby compiler that allows dynamic method redefinition with even basic C++-style vtables. The point is not the dispatch method but the ability to override them at will.
> The major difference is the dispatch based on signature hash (similar to e.g. Java interface method calls) instead of static offsets, which enables late binding
That late binding is an important part of it.
But you don't even need to deviate from static offsets to enable that late binding (you do need to do so if you want the ability to do dynamic interface-based inheritance, but even then you can use a vtable-like approach - see e.g. Protocol Extension: A Technique for Structuring Large Extensible Software-Systems, M. Franz, 1995 - which adds dynamic inheritance at runtime to Oberon) as long as the dictionaries/vtables/whatever you look them up in are mutable.
> Ii is, because it inspired both the Smalltalk branch and the more mainstream OO languages, and hence it makes sense to consider it as a possible ur-language in that sense.
I then went on to argue simply that because Smalltalk is at the root of a significant branch, I wouldn't have an issue with considering that an ur-language if one considers that branch important enough and/or consider message passing and late binding to be essential for a language to be object oriented, as opposed to having some object oriented features.
But I went on to again agree with you:
> I agree with you that Simula at least on the surface will seem more familiar to people familiar with ALGOL-derived languages with OO mechanisms than to Smalltalk.
To sum it up: I've argued that a reasonable case can be made either for Simula or Smalltalk depending on how you define OO, but that no well established definition of OO would make Self a reasonable candidate.
Maybe the author picked that one as being in the lineage of JavaScript?
Ending up with Javascript and especially Ruby as ALGOL derived makes no sense to me. Where more limited OO languages have imported OO aspects, JS and Ruby have wrapped ALGOL syntax and a few concepts around semantics that are much closer to Self and Smalltalk respectively.
[1] "OOP to me means only messaging, local retention and protection and hiding of state-process, and extreme LateBinding of all things".
[1] https://www.theguardian.com/education/gallery/2015/jan/23/a-...
https://en.wiktionary.org/wiki/ur-
Jesus! I thought these navel gazing low effort language posts went out of favor in 2012.
I know all of these languages and Forth isn't foundational for anything we currently use, maybe the JVM because it has a stack if you really try and torture the definition. It is just a collection of languages so the author can look smart.
If they wanted to provide insight, they would demarcate how the semantics of computation are different from each of these languages. It doesn't even mention the power of Forth in being able to extend the language and runtime from within the language itself.
I'll see myself out.
The prefix isn't even used properly.
an ill-educated whiner, and uncalled for.
Whenever I see a taxonomy I itch for some underlying logic that unifies them. If the explanatory scheme is successful then you might actually convert the taxonomy into a tree, where the root is some property that all of them ur-languages share etc.
In theory you can emulate anything on something that's Turing complete, can't you? It will just be harder or easier. May not be worth the trouble. But still possible.
foo = if bar
1
else
2
end"" Simula (1967) is generally accepted as being the first language with the primary features of an object-oriented language. It was created for making simulation programs, in which what came to be called objects were the most important information representation.
Also maybe command / shell languages deserve a mention?
;-)
They do not match any of the listed families, do they?
Looking at some TLA+ examples, I’d say, squinting a bit… somewhere around Prolog ? I’d love to hear someone chip in !
And of course, TLA+ has a suite of temporal operators used exclusively for declaring contracts in service of its role for verifying specifications. Which (as @hwayne points out elsewhere) stem from mathematics -- Linear Temporal Logic specifically -- and not programming.
TLA+ is similar to Prolog, extended with a next-state operator and the temporal operators (again, to serve its niche purpose).
(Though the main TLA+ implementation, TLC, doesn't implement logic variables as richly as Prolog does, so coding style within a relation tends to be almost a bit more imperative. They exist in the language though, via the unbounded ∃ operator.)
To take TLA+ as an example -- I've certainly written (short!) programs in both TLA+ and PlusCal -- both imperative (typically an implementation of something that I'm modeling) and declarative (to solve logic problems in the same manner I would use Prolog).
And there's nothing privileged about TLC's capabilities and limitations -- one could equally imagine a TLA+ interpreter which disallows cross-state nondeterminism or use of temporal predicates but allows interaction with the environment via a special variable or predicates, without needing to augment the language, thus allowing "normal" programs to be written and executed. Or a TLA+ interpreter which permits full nondeterminism via a SMT backend, which can act as a richer version of Prolog.
Imagine writing correctness-critical code snippets in TLA+, verifying them, then compiling and linking them into an application written primarily in another language! I don't believe there is anything about TLA+-the-language which would need to change for such a tool to exist.
There are of course language elements specific to verification in TLA+ -- namely the temporal operators pose a challenge to "synthesizing" an executable program. But I don't think this makes TLA+ any less a programming language than the existence of, say, Haskell's type language makes Haskell a programming language.
Verilog sits in a similar space, where it was designed for verification, and is full of constructs usable only for verification, but also has a "synthesizable" subset which can be and is used expressly for implementation without involving verification of any sort. Although one can argue Verilog is a hardware description language rather than a programming language, I haven't seen anyone argue Verilog is a specification language rather than either of those.
(Also, lots of spec languages are weaker than programming languages, to make verifying them easier. IIRC mcrl2, FDR, and Promela all fall under this category.)
I still think there are practical differences, though, between a language designed for the purposes of specification and one designed for implementing programs. TLA+ has temporal predicates and cross-state nondeterminism because it makes modeling at a high-level easier, even though it makes programmatic implementation harder. That leads to different design decisions through the language.
More importantly to the OP, thinking of them as fundamentally different is useful because they draw on different inspirations. It could be that PLs and SLs converge later, but their origins and lineages are still different. And that's interesting and worth studying.
I don't think talking about ur-languages is as useful as talking about archetypes: "ur-" to me implies "the original", and that's a hard thing to do. TFA has some ur-s like self that aren't the original, but still are useful as archetypes. Off the top of my head, the archetypes of formal speciifcation would be
* Temporal Logic (LTL, CTL, TLA)
* Relational algebra (Z, B, Alloy)
* Guarded Command Language (Promela, SPIN)
* Process Calculi (CSP, FDR)
* Labelled Transition Systems (Petri Nets, mclr2)
* Just drawing a diagram and figuring out the semantics later (UML)
* Abstraction over an existing programming language
This is all real messy, and I see lots of overlapping concepts and unclear cases. Are state machines their own thing, part of LTS, or an implementation detail of the specification approach? Should we be distinguishing between the mathematical formalisms and the languages themselves? What about the vaster world of specifying code and not designs? etc etc etc