[HN Gopher] Features of PL/I not realized in a modern language
___________________________________________________________________
Features of PL/I not realized in a modern language
Author : vitplister
Score : 44 points
Date : 2021-11-26 17:10 UTC (5 hours ago)
(HTM) web link (minnie.tuhs.org)
(TXT) w3m dump (minnie.tuhs.org)
| throwawayboise wrote:
| Used PL/1 in one of my first programming classes at university.
| Never before or since, but I don't recall it being a bad language
| to work in.
| jrd259 wrote:
| PL/1 was the systems programming language for Multics.
| Certainly not a bad language to work with.
| pjmlp wrote:
| And plenty of other systems as well.
| reidacdc wrote:
| PL/I was the software-engineering language in my undergraduate
| computer science classes also, although it was beginning to be
| displaced by Pascal in the introductory courses, and C/C++ in
| the advanced courses. (Mid-1980s, in case that is not already
| clear.) It hung on because, we were told, it was still in
| demand in industry, particularly at telephone companies.
|
| I did a group project with it, and the language was large
| enough that we found that each member of the group seemed to
| have learned a slightly different subset of it.
| throwawayboise wrote:
| Similar timeframe. Used PL/1, Pascal, and Modula-2 in three
| successive semesters IIRC.
| manbart wrote:
| I think 2,3,5 and 7 apply to Rexx as well, another IBM created
| language. I wouldn't call it modern however
| npsimons wrote:
| > I wouldn't call it modern however
|
| I wonder how the claim stands up if we remove that "modern"
| adjective. Or perhaps consider that "modern" languages don't
| have some of these features as they turned out to be "harmful"
| (see Dijkstra's "GOTO considered harmful") - at least one reply
| in that thread calls out one of these "missing features" as
| very dangerous, sort of like how some C++ projects will forbid
| features (private inheritance, etc) for ease of maintenance.
| adrian_b wrote:
| There are many other features of PL/I besides those mentioned
| there, which are not encountered in modern languages.
|
| Because PL/I was designed by IBM for their computers and
| operating systems, there were no concerns for portability, so the
| language definition included a large amount of features that are
| left unspecified in standards for languages like C/C++, because
| they are available only for certain operating systems or
| computers, so the standard must be restricted to specify only the
| minimum features that are available everywhere.
|
| For example the C++ threads are limited only to the features
| provided by the POSIX pthreads, even if most operating systems
| have additional features for multi-threading. PL/I on the other
| hand, already in 1965 had more powerful features than the C++
| threads.
|
| One feature of PL/I that is completely absent in all modern
| languages is what I consider to be the right way for error
| handling.
|
| Instead of being forced to test the return value of a function to
| determine what error might have happened, which is a redundant
| operation, as this was already tested once in the invoked
| function, and which clutters the code, obscuring the processing
| of the normal path, the PL/I method was to group the error
| handlers at the end and pass the labels of the error handlers to
| the invoked function as alternative return labels.
|
| While this looks visually very similar to a _try_ block with
| exception handlers, the hardware implementation in PL /I is far
| more efficient than in modern languages, because there are no
| conditional branches and no extra code to determine where is the
| appropriate error handler, just the return, which is done anyway
| when the invoked function finishes, jumps to different addresses
| in case of errors, instead of returning to the point of
| invocation.
|
| PL/I also had exceptions, but those were used for really
| exceptional conditions, like numeric overflow, illegal memory
| accesses etc., not for correctable error conditions, like a
| mistyped user input or a file that cannot be found, which are
| best handled at the point where a function has been invoked,
| because only there you have complete information about the
| intention of the failed operation.
| p_l wrote:
| PL/I model was inspiration for Common Lisp condition system,
| which handles both recoverable (continuable) and non-
| recoverable conditions (akin to exceptions). Continuable
| exceptions are also a thing in Windows NT, though rarely seen
| in practice.
|
| All of those hark back to PL/I error handling, possibly through
| Project MAC (which was probably bigger PL/I user than anything
| from IBM for a time)
| PaulDavisThe1st wrote:
| > For example the C++ threads are limited only to the features
| provided by the POSIX pthreads, even if most operating systems
| have additional features for multi-threading.
|
| What facilities are you thinking of?
|
| > PL/I on the other hand, already in 1965 had more powerful
| features than the C++ threads.
|
| Given that the term "threads" originates in the 65/66/67 era,
| this is an interesting claim.
| adrian_b wrote:
| The term "thread" has become fashionable only after 1990.
|
| Nobody used the terms "thread" or "multi-threading" when PL/I
| was designed. Everybody used the terms "task" and "multi-
| tasking", with the same meaning.
|
| So in PL/I you have "tasks" for what are now called
| "threads".
|
| The most annoying feature of the C++ thread support library,
| which is inherited from the POSIX pthreads specification, is
| the pathetic operation _join_ which waits for the termination
| of a designated thread.
|
| This is almost never what you want. The most frequent case is
| to wait for the termination of anyone of a group of threads,
| which cannot be done with _join_.
|
| Already in 1965, PL/I had a _wait_ that could do anything
| that can be done with _WaitForMultipleEvents
| /WaitForSingleEvent_, i.e. it could wait for any or all of
| any combinations of a set of events, e.g. thread
| terminations.
|
| This is such a basic feature that I cannot understand how it
| could have been omitted from the POSIX pthreads and in
| consequence also from the C++ thread support library. The
| other features of pthreads/C++ that can be used to implement
| such a functionality, e.g. condition variables, also suck
| badly in comparison with a good _wait_ for events.
| spacechild1 wrote:
| > This is almost never what you want. The most frequent
| case is to wait for the termination of anyone of a group of
| threads, which cannot be done with join.
|
| I also have to say that this doesn't match my experience at
| all. Typically, my threads run for the whole duration of
| the program/object and I only need to join them when the
| program/object is done. In fact, this pattern is so common
| that C++20 added std::jthread which automatically joins the
| thread in the destructor.
|
| I literally never had to wait for "the termination of
| anyone of a group of threads". For you it's apparently the
| most frequent case. This only teaches us not to make too
| broad assumptions. The world of programming is large.
| PaulDavisThe1st wrote:
| It's clear that you know a lot about this stuff, but I
| think maybe not quite enough.
|
| The Linux kernel retains the term "task" for "execution
| context", and the term "thread" is used exclusively in user
| space purely because of programmer conventions (pthreads,
| specifically). This makes a limited amount of of sense
| because "thread" has also come to mean things that are very
| definitely not full execution tasks (user-space and/or so-
| called "green" threads). This overloading of the
| terminology in user-space is not reflected in kernels, but
| allows for developers to play with alternate
| implementations without changing their fundamental
| terminology. After all, there some things where user-space
| threads are precisely what you want (and these absolutely
| do not correspond to kernel tasks).
|
| > This is almost never what you want.
|
| I would beg to differ. I've been writing multithreaded code
| in C++ for nearly 30 years, and I don't remember any time
| that I wanted to join on a group of threads. My current
| project (20 years old) is heavily multithreaded (typically
| 20-60 threads) and there is nowhere that a thread primitive
| to join on a group of threads would make our lives easier.
| The only context where I could even think of using it might
| be to wait on a thread pool, but in general this tends to
| be unnecessary and if it is, can be trivially handled
| without a multi-join.
|
| > Already in 1965, PL/I had a wait that could do anything
| that can be done with
| WaitForMultipleEvents/WaitForSingleEvent,
|
| WaitForMultipleEvents/WaitForSingleEvent are not part of
| the C or C++ language, but Windows system calls. While
| their semantics are very powerful and useful (which
| obviously would thus apply to PL/1's _wait_ ), their
| absence cannot be laid at the feet of C or C++: no Unix-
| like OS has had this operation, ever (it is almost possible
| with contemporary Linux).
|
| You could make the argument that it's not part of pthreads
| because this operation was never a part of posix, and so
| pthreads could not build on it (you can use those calls on
| Windows even in a single-threaded task, and that would be
| true in a POSIX system if the call existed). So it really
| has almost nothing to do with pthreads at all, other than
| that one could imagine a pthread wanting to block on "wait-
| for-something-anything".
| monocasa wrote:
| Linux exports the concept of threads in it's user facing
| ABI. You can see this in "Thread Group ID" and "Thread
| ID" values.
| adrian_b wrote:
| > The Linux kernel retains the term "task" for "execution
| context"
|
| This has historical roots from the first UNIX versions
| written for DEC computers. Even if the UNIX authors
| usually preferred the term "process", in the DEC
| documentation for their computers and operating systems
| the term "task" was always used for "process". So the
| term was also used in UNIX in various places.
|
| > I don't remember any time that I wanted to join on a
| group of threads
|
| This is not surprising, because there are many kinds of
| multi-threaded applications and many styles of
| programming them.
|
| I do not doubt that what you say is correct for you
| applications, but my experience happened to be opposite.
| I have never encountered a case when I wanted to join a
| single thread, but I have encountered a lot of cases when
| I wanted to join any of a group of threads (e.g. for
| keeping a number of active threads matching the number of
| available cores) and also a less number of cases when I
| wanted to join all of a group of threads, typically at
| the end of an operation. The latter case is less
| important, because it can be done by repeating a _join_
| with a single thread, even if that is much less efficient
| than a _wait_ that waits for all.
|
| > are not part of the C or C++ language, but Windows
| system calls
|
| This is precisely what I have already said in my first
| post, i.e. that standardized languages like C/C++ are
| forced to specify only the minimal features that are
| available on all operating systems, so they cannot
| include _WaitForMultipleEvents_ , while PL/I was free of
| such portability concerns, so it could specify more
| powerful features.
| PaulDavisThe1st wrote:
| > This is precisely what I have already said in my first
| post, i.e. that standardized languages like C/C++ are
| forced to specify only the minimal features that are
| available on all operating systems, so they cannot
| include WaitForMultipleEvents, while PL/I was free of
| such portability concerns,
|
| I think you missed my point. WaitForMultipleEvents is not
| part of a thread API on any platform. It's a part of the
| platform API, and is used by single-threaded and multi-
| threaded code. There's no reason for pthreads (or any
| other thread API) to represent this system call, because
| the system call either exists, and can be used directly,
| or does not exist, and cannot be used.
|
| In essence, you're really just noting that POSIX (not
| pthreads) never had a wait-for-just-about-anything API.
| That's a legitimate complaint, just not very relevant for
| multithreaded programming.
|
| > This is not surprising
|
| Well, given that you said _" This is almost never what
| you want._", I'd count it as least a little surprising.
| My point was that multi-join is not "almost never what
| you want", but has always been "useful in certain
| contexts". I have never come across a multi-join API that
| blocks until all threads have completed (they typically
| return when any of the specified threads completes), and
| so the difference in efficiency for this version of
| multi-join is essentially identical to a loop+single-
| join.
|
| >This has historical roots from the first UNIX versions
| written for DEC computers.
|
| I don't see much evidence for this claim. task_t exists
| in early versions of AIX and Mach, and the terminology
| was already common in Multics (as you know). I don't
| think that Linux' use of task_t has any relationship to
| the Ultrix use, but maybe you have some specific insight
| here?
| gnufx wrote:
| Is the PL/I alternate return fundamentally different from
| Fortran's (or FORTRAN's in those days)?
| pjc50 wrote:
| > the PL/I method was to group the error handlers at the end
| and pass the labels of the error handlers to the invoked
| function as alternative return labels.
|
| The idea of an alternative return address - that is, allow the
| calling convention to pass more than on return instruction
| pointer - seems both simple to implement and really useful. I
| guess in the modern gotoless world the trouble is structuring
| the syntax around the call.
| drran wrote:
| It depends on which part of code will clear stack after call.
| If caller is responsible to clear stack, like in C, to
| support variable arguments, then called function must return
| to the original point of call.
|
| If called function is responsible to clear stack, like in
| Pascal or Forth, then yep, multiple return points are easy to
| implement and useful to implement something like switch
| operator. I used it in Pascal, when I was in school. Just pop
| old return address from stack and push new one.
| adrian_b wrote:
| You are right, but the ancient _cdecl_ function call
| convention used by C for variable argument lists is really
| obsolete and it should never be used in any new programming
| language implementation, even if it has survived until
| today in various ABI specifications.
|
| Like for the C null-terminated strings, there are many
| alternative ways to implement variable argument lists and
| all of them have already been used many years before the
| first C compilers, e.g. passing a hidden parameter count or
| implementing transparently for the programmer any _printf_
| -like functions as _vprintf_ -like functions.
|
| With the alternative implementations, the stack should
| always be freed in the invoked function, which also enables
| various other important features, e.g. tail-call
| optimization.
| adrian_b wrote:
| The alternative return addresses were declared in the
| function prototype as parameters having the type _label_.
|
| Then you just passed them during function call, e.g. "handle
| = open_file(input_file_name, open_flags,
| FailedToOpenInputFile);"
|
| The error handlers at the end of the body of the invoking
| function were just labelled appropriately.
|
| Writing a label like _FailedToOpenInputFile:_ before an error
| handler is certainly much simpler and more obvious than
| strange workarounds for exception identification, like
| inventing a dedicated data type for each possible error, as
| in C++.
| ptx wrote:
| > _4. A named array is normally passed by reference, as in F(A).
| But if the argument is not a bare name, as in F((A)), it is
| passed by value._
|
| Visual Basic (VB6/VBA) does exactly this, with the same syntax
| (except without the outer parentheses if the call is a
| statement).
| kergonath wrote:
| Fortran does it as well, I believe. The reason is that (A) is
| an expression whose value is that of A, and not a variable.
| This includes Fortran 2018; I am not arguing whether this is
| modern or not.
| gnufx wrote:
| Fortran actually passes by value-return, or whatever you call
| it, not by reference. The call may still be compiled by
| reference, of course, and it used to be fun to change the
| value of a number in the days when it wasn't in read-only
| storage.
| throwawayboise wrote:
| It seems quite reasonable and intuitive to me.
| bigbillheck wrote:
| > 7. Astonishingly complete set of implicit data conversions.
| E.g. if X is floating-point and S is a string, the assignment X =
| S works when S = "2" and raises an exception (not PL/I
| terminology) when S = "A".
|
| This seems like an extraordinarily bad idea.
| layer8 wrote:
| It's like implicit numeric conversions in C, just extended to
| string types.
| zokier wrote:
| Its not like the implicit numeric conversions in C were
| particularly good idea either
| emmelaich wrote:
| It was a mistake in C++ too, fixed with the _explicit_
| keyword.
| jrd259 wrote:
| Perhaps a bad idea, but at least it was rigorously specified. I
| note that that Javascript and perl have same ability. See also
| https://www.destroyallsoftware.com/talks/wat
| layer8 wrote:
| Javascript and Perl are different in that they allow a
| variable to change it's type by reassignment (i.e. they would
| allow the X = S assignment for S == "A"), which PL/1
| apparently doesn't. Weak/strong typing and dynamic/static
| typing are different axes.
| drfuchs wrote:
| And all the implicit conversion rules between pairs of types
| couldn't fit on a single-page chart; the official IBM manual
| included an honest-to-goodness fold-out bound right in. Fun
| times.
| ptx wrote:
| It does. It sounds like the Unicode handling in Python 2, where
| your program would still appear to work if it confused bytes
| with codepoints (through implicit decoding/encoding), until you
| get a non-ASCII input somewhere and everything blows up with a
| UnicodeDecodeError somewhere far from where the problem is.
| npsimons wrote:
| Upvoted simply out of controversy and curiosity.
|
| It's been a long time, and I've not delved deeply in to a wide
| swath of languages (recently), but surely the features he's
| citing are in other languages? Hell, even the list of "features
| only Lisp has" keeps shrinking by the decade. Some of these PL/I
| "features" feel like things you could trivially implement in a
| library in other languages (ie, not have to implement half of
| PL/I to get those features). Some of it sounds simply like
| syntactic sugar, and not actual features.
|
| What am I missing here?
| Someone wrote:
| Another feature: keywords aren't reserved words. For an example
| of where that can lead to see https://multicians.org/proc-
| proc.html
|
| The thinking behind that was that the set of keywords would grow
| over time, and that you couldn't expect any programmer to know
| all of them.
|
| There likely also are few programmers who do know all of them, as
| there are hundreds (see
| https://www.kednos.com/pli/docs/reference_manual/6291pro_042...
| for those of one implementation)
| curtisf wrote:
| Java has been using this approach with new reserved identifiers
| like `var` and `record` in the interest of backwards
| compatibility.
|
| JavaScript has done something similar with its `await`, `let`,
| etc
| teddyh wrote:
| Point 7 simply means that PL/I is very weakly typed. However,
| weak typing seems to now be unused in all modern languages;
| strong typing seems to be the current norm. (Not to be confused
| with static/dynamic typing, which is orthogonal.)
| the_only_law wrote:
| > Bit strings of arbitrary length, with bitwise Boolean
| operations plus substr and catenation
|
| This one actually piqued my interest in PL/I recently. Closest
| thing I've seen in modern languages is bitstrings in the BEAM.
| p_l wrote:
| I believe both Ada and Common Lisp also share this ability,
| though Ada has nice support in its type system for doing things
| like mapping a portion of mmio register as bitstring that gets
| further defined as new type (whether enum, or integer, or
| string etc.)
| throwawayboise wrote:
| Yes I also thought of Erlang bitstrings when I read that.
___________________________________________________________________
(page generated 2021-11-26 23:01 UTC)