My first college-level software engineering courses were in Modula-2, and I think Modula-2 gave me an early kick in the pants towards some good practices.
This was shortly before Java introduced large numbers of CS departments to OOP, but some CS departments had been teaching abstract data types (ADTs), modularity, etc.
I didn't like the syntactic verbosity of Modula-2, nor the very limited system access in the setup we had, but the emphasis on separating interface and implementation was great.
We had school assignments to do weeks/months-long team software development exercises, in which you could only show the other team members your API docs and give them your interface files (not implementation), and the team's application was supposed to come together and work at the end. The project I remember was to build a spreadsheet program in Modula-2, as a team, not being able to look at or change other student's code. Whether you were new to programming, or you'd taught yourself and were programming individually since childhood, this was almost certainly a new discipline to you.
I was soon using C professionally, as an intern, and adapted some of that bit of module discipline from Modula-2 to C. While an intern, I recall telling one of the senior engineers/managers that I thought learning Modula-2 improved my abilities as a C programmer, and that everyone would benefit from learning Modula-2. I recall her smiling and lowering her voice while she said quickly, "Don'ttellanyonethat." :)
These were some of the better software developers in the industry, working on high-end CPU in-circuit emulation, with integration with workstation network CASE, and I was just an overly-enthusiastic/energetic teen/mascot. And I didn't yet understand how much more they knew about this area, and about other things I had no concept of. But Modula-2 discipline was still a good boost for an aspiring software engineer at the time.
An aspect of Modula-2 that I find so interesting is its support for coroutines. Is there any modern and permissibly licensed (GPL, BSD, MIT, Mozilla ...) implementation that has an efficient implementation (low context switch time). I am not looking for anything that rides on pthreads api or library, or anything that maps to OS threads.
I think one of the more novel (hah! 40 years old now) aspects is that tasks can be declared on the stack which allows for structured concurrency to be done in Ada without needing a separate library. Something like (outline):
package body SomethingSomethingDarkside is
procedure Foo(N : Integer) is
task A ...
task B ...
task C ...
task type D ...
Bunch_Of_Ds : array (1..N) of D;
begin
DoStuff;
end Foo; -- doesn't actually terminate until all the above terminate
begin
Foo;
Put_Line("Won't be printed until after all of Foo's tasks finish.");
end SomethingSomethingDarkside; --
Neatly fits the intention of Structured Concurrency. Tasks can also be allocated on the heap which allows you to have something more like what conventional threads or Go routines or Erlang processes do.
And protected objects come with built in mutexes and blocking conditions on entries to prevent race conditions or control system invariants.
One of the variety languages I used in my CS undergrad in the late 1980s. From what I remember this included at least PL/1, 6809 assembly, C, Pascal, Fortran, Modula-2, and Scheme.
Its not at all bad (I learned programming using FreePascal, true story). But its unit system is from UCSD pascal (via TurboPascal, etc). I'd say Wirth has a certain 'signature style' and I don't think that ObjectPascal's additions really match that.
Also, IIRC, Modula-2 and Oberon feature GC, etc. and personally while I liked Pascal back then, for most stuff I do, not having a GC is pretty-much a regression.
Modula-2 doesn't have a GC, it is just like Pascal in that regard.
Modula-2+, which was developed by the same group that did Mesa/Cedar, does have GC.
However Modula-2+ was short lived and eventually grew into Modula-3, also by the same group of researchers.
As for Oberon, while it was a very nice system for 1992, it doesn't make sense in 2021, specially given its limitations for low level programming. There are a couple of ETHZ papers that talk about its GC limitations.
From the Oberon linage, Active Oberon is the best one, which adopts the same concepts as Modula-3 for low level control of the GC, with safe pointers under GC control and unsafe ones used for C like tricks like DMA transfers. This kind of language features were already present in Mesa/Cedar, but Wirth didn't want to complicate his Oberon design.
So in Oberon you are left with explicit calls to dispose resources or hope that the callbacks registered to the finallizer actually happen to run some day.
By the way, there were some Modula-2 extensions that did add support for GC, but I am not aware of any major compiler, specially given Modula-3's existence.
https://news.ycombinator.com/item?id=26520996