37 comments

[ 2.9 ms ] story [ 89.8 ms ] thread
Any comment on what is so special about this?
If you ever wondered where Go took a lot of good ideas from, look at Modula / Oberon.
In the code the link points to, I just see another C-like language.

What "good ideas" should I be looking at? I am not Oberon expert, but in any other language I'd say this code is pretty bad:

- logic errors: We keep going even if error occurs (none of the error checks have return in them).

- In two different source files, "System" is imported under two different names. Is this just to confuse the readers?

- Hardcoded string length (Mod.s:17). If someone to change the message, you have to be very careful to update the length.

- Arbitrary constants (why '2' in Mod.s:23)?

> In the code the link points to, I just see another C-like language.

To be fair Oberon's roots go back to Pascal and not C. Pascal predates C.

:)

I used to program in Pascal long time ago (Turbo Pascal dialect), but the link does not show any of Pascal-specific functionality. Where are types with unusual range (var i: 0..15)? Where are arrays with non-zero based ranges? Where is the neat interface/implementation separation? Is there runtime-based range checking? Because I do not see it, and I bet if I changed this "17" to "117", it will just send extra 100 bytes of un-initialized memory over the socket.

This particular code does not have any pascal-y language features. What is the point of the post? If it was to demonstrate that Oberon is good for teaching / real stuff / general purpose programming / something else, then it failed.

To me, the points of the post is: "hey, this language is not dead! We can now fork() and do socket ops!". Is this intentional?

The hard-coded string length is some test code. It's not an indicator of a weakness in the language. Oberon has Strings.Length(), which returns the length of a string.

The OP's link points to a test directory. Go look at the actual source code:

    https://github.com/norayr/voc/tree/master/src
Oberon has all the features you describe: Range types, bounded arrays, and so on. It doesn't have the interface/implementation split (though it has a "DEFINITION" block you can use); modules contain both the interface and implementation, and you export symbols with "*" (or "-" for read only).

Basically, Oberon-2 > Oberon > Modula-2 > Modula > Pascal. Oberon adds interfaces, open arrays, type-bound procedures (similar to Go's struct methods), etc. It's an evolutionary improvement/tweak over the previous languages. If you know any of them, Oberon-2 will be very familiar.

Oberon and its siblings are awesome. I believe that to fully appreciate Oberon & friends, you need to have a bit of context on its creation and usage.

Oberon is a language by Niklaus Wirth, so it is not a C-like language, it is a different branch of the Algol family, it is the Pascal branch were we have Pascal, Modula and Oberon (in their different versions).

This family of languages are very easy to port and to construct performant compilers. They are not only easy to learn from a student point of view but also can be used for real useful work.

In its home university in Zurich, they created the oberon system which is a computer hardware, operating system, programming language and userland applications solution that they used all over the university. This was done when GUIs and the Mac were in its starting years (circa 1987).

Sometimes I feel that Pascal and its successors are not fully appreciated by our world that grew too enamored with C. There were some awesome ideas and solutions created there in Switzerland at that time when create a full package of hardware and software was something that people still tried to do.

If you're interested in exploring things further here are some links:

https://en.wikipedia.org/wiki/Oberon_(operating_system)

https://schierlm.github.io/OberonEmulator/

http://www.projectoberon.com/

>Sometimes I feel that Pascal and its successors are not fully appreciated by our world that grew too enamored with C. There were some awesome ideas and solutions created there in Switzerland at that time when create a full package of hardware and software was something that people still tried to do.

Having been taught Modula-2 in Programming 102 class in college (this was a year ago), I disagree completely.

Software development has this camp with a contrarian, 'this was done at Xeroc PARC' mentality where we're using terrible computers because Smalltalk or the Lisp machine or Oberon failed and nasty Unix took over. But when you actually have to use these languages, well, they're terrible. They're C, with all its flaws, but a more indented/structured syntax.

Pascal, Modula, and Oberon have no lessons that have not been learnt by some or another language, and done better.

No, they are not C with all its flaws. They offer strict type checking, preventing a lot of constructs which would have unintended consequences when passing a C compiler. Modula introduced a proper module concept, without the need for header files without their issues, allowing for fast separate compilation of modules. Go picks up a lot of these ideas and brings them to a modern language.
> They're C, with all its flaws, but a more indented/structured syntax.

There are many reasons to criticise aspects of Modula-2 or Oberon, but saying "they're C, with all its flaws" totally misses the mark.

The entire language family belongs to a very different tradition. Wirth spent most of his career removing features, and adding much less than he removed. For good reason: Oberon as a language contains the bare minimum that his experience with systems-engineering (the OS and all applications for the Oberon systems that ran ETH Zurich for many years were written in Oberon itself) showed that they needed.

This went down to the compiler: Unlike languages like C, Oberon is designed to be easy to compile. The grammar is positively tiny - it fits on 1-2 pages of A4 in a readable font, with comments. It can parsed and compiled in a single pass with a clear separation of the lexical scanner and parser, without any hacks or ambiguity, and I believe only with a single token lookahead. C isn't by any means a horrible language to compile compared to most newer languages, but compared to Oberon it's a monstrosity.

This design philosophy follows through from the compiler through the support libraries, through the OS.

It's austere, yes (the Oberon-07 language report is 17 pages...), and if you don't like austere languages, you won't like Oberon. But that too is a feature that separates it from C.

As someone else pointed out, Go is a much better comparison than C. Oberon is garbage collected, for example, which instantly put it very much in a different "camp" to C back when it was launched, in an era where the thought of using garbage collection for a systems programming language was seen as something only the weird dynamic typing lisp and smalltalk people would consider.

What it comes down to is that Wirth has always understood the difference between simple and jury-rigged in a way that Pike and the Unix crew never have.
Can you elaborate on this "Unlike languages like C, Oberon is designed to be easy to compile?" I thought that was one of the reasons that C looks the way it does was to be easier to compile
I mentioned some already, but to reiterate and expand.

- The grammar size. This is the complete EBNF for Oberon 07 [1]. For comparison, here is a C 2011 grammar [2]. C is quite simple to parse, but still more complex than Oberon.

- The Wirth languages can generally be compiled in a single forward pass. Dialects that allow forward references tends to require them to be specifically declared. Explicitly specified forward references minimize the amount of state the compiler needs to hold on to to fix up the typically minimal amount of references, but Oberon 07 doesn't have them anyway. This doesn't matter so much now, but in the 80's it was a big deal to be able to do single pass compilation without lots of memory spent on keeping a lot of bookkeeping or a syntax tree around (the Wirth compilers typically never constructs an AST, but directly emits opcodes during parsing by calling into a code-generation module, at the cost of losing out on quite a bit of optimization opportunities; on the other hand it also makes the compilers very simple and blazing fast).

- No preprocessor.

- In C, the presence of typedef means that there are constructs that are semantically ambiguous until you consult the symbol table (the parsers can still be context free, but you can't fully resolve the type of symbol in certain situations until you look at the symbol table). Also not a big deal, but things add up.

To be clear, C is one of the easier languages to parse. I'm not implying C is particularly complex. The verbosity of Yacc/Lex vs "straight" EBNF overstate the complexity of the C grammar somewhat. And compare it e.g. to C++ or (run...) Ruby (which I love dearly to use, but the grammar is a crime against parser writers everywhere) and it's trivial. But Oberon took it several steps further.

C is what you get if you build something through accretion and gradual changes while experimenting, despite trying to contain complexity. The result is small, but not minimal.

Meanwhile modern Oberon is what you get when you go on a 40+ year quest (even though Wirth is 82 years old, the latest revision of the Oberon 07 language report was May 3rd this year! [3]) to eliminate every unnecessary feature from an Algol-style language while adding only the bare minimum in its stead. The Oberon dialects (Oberon, Oberon-2, Oberon 07 - the latter is the newest and deriving from Oberon rather than Oberon-2) are strictly smaller languages than their by now distant ancestor in Pascal, despite being substantially more powerful.

Consider that the language report for Oberon-07 is 17 pages including the table of contents and introduction, and despite duplicating the language productions both in each chapter and in an appendix...

I prefer less austere languages these days (Ruby...), but I wish more language designers spent more time studying Wirth's work and actually paid attention to how their design affected parsing and code generation complexity.

[1] http://oberon07.com/EBNF.txt

[2] http://www.quut.com/c/ANSI-C-grammar-y.html for the Yacc grammar, and http://www.quut.com/c/ANSI-C-grammar-l-2011.html for Lex

[3] https://www.inf.ethz.ch/personal/wirth/Oberon/Oberon07.Repor...

Thanks for the detailed response. I appreciate it. I intend to look into this branch a little more when I have some time. Cheers.
>But when you actually have to use these languages, well, they're terrible. They're C, with all its flaws, but a more indented/structured syntax.

That couldn't be further from the truth.

The hard-coding of the length of the "aff" string and/or re-use of the "s" (size) variable does indeed look odd:

   s := SIZE(String);
   aff := "Affirmative, Dave";
   (...)
   s := 17;
   n := Unix.Write(sock, S.ADR(aff), s);
As far as I can gather from: https://github.com/norayr/voc/blob/master/src/lib/v4/Strings... this implementation uses some kind of c strings (not familiar with Oberon in general, but I thought that the norm was something like Pascal strings?): "Strings provides a set of operations on strings (i.e., on string constants and character arrays, both of which contain the character 0X as a terminator). All positions in strings start at 0. Strings.Length(s) returns the number of characters in s up to and excluding the first 0X."

It would seem that:

  s := String.Length(arr);
should be the sane equivalent to "s := 17;"
Good ideas from the language(s), not the source code linked.

* Coroutines, a crown jewel of Go.

* Proper modules, separate files for interface and implementation parts.

* Nicer type system, with ranges, enumerations, ordered types and arrays indexed by any ordered type; can't remember if it ever had sum (aka variant) types.

* IIRC Oberon had an 'object system' very similar to Go's, with functions having a receiver and struct-based 'classes'.

* Obviously far fewer (if any) undefined behaviors than C.

* Low-level access; it was entirely possible to write an interrupt handler in Modula-2, access address-mapped ports, etc.

>Proper modules, separate files for interface and implementation parts.

Proper modules are great, but I wouldn't call the "separate files for interface and implementation parts" a good feature. That's a throwback to languages like C.

It's not DRY, and it's also busywork -- the interface can trivially be created/inferred from the implementation file alone.

It was easy to give your customers the proper interface files (they were compilable), so they could code against your interfaces. They are much lighter than the object files (it was the time of the floppy disk and 2400 modems).

One point of having interface files separate is tools like `make`. If the interface file's timestamp did not change, the interface is the same.

It would probably be nicer to have two compilation artifacts, the extracted human-readable interface part and the object-file implementation part.

It (in my case TopSpeed Modula-2) was waaaay faster than e.g. Borland C++ wading through endless .h files.

>It was easy to give your customers the proper interface files (they were compilable), so they could code against your interfaces. They are much lighter than the object files (it was the time of the floppy disk and 2400 modems).

And how would they test/run their code, since the interfaces would at best link, but not run anything, being only stubs?

>It would probably be nicer to have two compilation artifacts, the extracted human-readable interface part and the object-file implementation part.

Yes, if interface-only coding was possible for the clients, and disk space was at a premium yes. Anything but having to manually write interface AND implementation.

You could generate a stubbed implementation from the header file, I guess.
> It would probably be nicer to have two compilation artifacts, the extracted human-readable interface part and the object-file implementation part.

Oberon implementations traditionally generates symbol files, that while I believe they are usually binary tends to have tools to generate textual representations, and there are plenty of tools to generate versions of the modules with implementations stripped as well. To compile a module you should need only the symbol files of the modules you import, not the compiled modules themselves.

Oberon does'nt have separately written interface and implementation files, although Modula did. In Oberon, an interface file was derived from the implementation file (or from the object file), not written by the programmer.
It's also a "feature" that Oberon removed.
Hello,

>another C-like language

Oberon is also a system programming language.

I guess, the code would not look as C-like to you, if it would use some threading or socket library, may be written in "oop" way.

It might create an impression of being close to C because it uses the same way of dealing with sockets as people do in C usually. If the code would use TThread and TSocket classes, like in Object Pascal, it might look different.

It requires less efforts to create wrappers, than to write libraries. Probably if there were more "speakers" of the language, then there would be more ready to use libraries for developers.

This also may explain why that code could be improved, as you have pointed out - it serves more as an example of how to write a wrapper, rather than example of how to, or not to, program in Oberon.

I also know that the code could be improved, I can add that for instance I do not like that the main server module imports SYSTEM module, which flags it as unportable and potentially dangerous. But never thought that it could appear at such a crowded space as HN and deferred the improvements. Also, I did not think the code deserves a lot of attention.

>What "good ideas" should I be looking at?

To me it is important that it's low level and system programming language which, if compared to C, is still is more safe and less error prone.

It is important to me that Oberon is modular. That types, variables, functions are "private", unless explicitly exported. Strong typing. Type checks across module boundaries. In Oberon systems I could point out dynamic module loading, tasks vs threads, active objects in the language, single address space operating system is controversial but interesting. These are good ideas I can mention without even thinking.

Where is pjmlp when we need him?
In college I wrote a (miserable) Modula-2 compiler. Its funny seeing Oberon-2 being used in the wild but brings back a touch of nostalgia.
OT: This is very nice code. It reads very well.
How do you type these ALL CAPS keywords, isn't this very daunting?
Personally I hold the shift key with one of my fingers.
TextExpander or other macro generators are your friend. Type one keystroke, and a few dozen characters appear.
Significant caps or caps used by convention to separate syntactic elements was quite common for a while when syntax highlighting was still relatively rare. E.g. AmigaE [1] is another language which heavily used caps (correct case was a compulsory part of the syntax).

You get used to it quickly, but I can't say I miss it.

[1] http://strlen.com/amiga-e

(comment deleted)