This looks like it may wind up reinventing select(2), for which one should maybe consult poll(2) to see where that approach wound up.
The underlying problem is that Oberon lacks an unordered select (WHILE ELSIF comes close but has the unfortunate —in this case, but not for many actor scenarios— repetition). Maybe something could be done with a CASE Poll(...) OF ... END?
If life were a Ponyhof, I would ask for something along the lines of:
PROCEDURE sendit(text: STRING): BOOLEAN;
VAR fail: BOOLEAN;
FailChan: CHANNEL OF BOOLEAN;
BEGIN
fail := FALSE;
FailChan := After(TimeOut);
SELECT
SENDABLE(TxChan) THEN SEND(TxChan, text) |
RECEIVABLE(FailChan) THEN RECEIVE(fail, FailChan)
END
RETURN fail END sendit;
Thanks. I'm neither happy yet with the SELECT() function.
Concerning your proposal, I would rather not introduce a completely new control structure. But one could - like Wirth with the type CASE statement in Oberon-07 - reuse the WITH statement like:
WITH
SEND(TxChan, text) DO
| RECEIVE(FailChan, fail) DO
END
where I would just have to change the Guard a bit like
I had a read of the page after you updated it. I find the proposed SELECT function to be unwieldy, to the extent that one would really want some syntax added instead.
Assuming I'm correctly inferring from the above, the WITH looks better. Presumably one could have multiple SEND and/or RECEIVE function clauses in the WITH statement, so acting as an equivalent to the Go Select?
What would ELSE do? It it acting the same as the default clause in a Go select, i.e. allowing for a non-blocking call?
The one other question with the above is how would one detect a closed channel when the RECEIVE clause hits?
> What would ELSE do? It it acting the same as the default clause in a Go select, i.e. allowing for a non-blocking call?
Correct, that's the idea.
> how would one detect a closed channel when the RECEIVE clause hits?
EDIT: I added yet another build-in procedure which returns the closed state of a channel. A blocking WITH would continue if no channel was ready to communicate, but at least one was closed.
Is this really an issue? So far I didn't care much about close or its synchronization. Isn't just calling CLOSED good enough? Do we really have to be exactly sure when CLOSE was called? If it was an issue I would rather do without CLOSE.
EDIT: or maybe just allow CLOSED guards in WITH, similar to SEND or RECEIVE, like
WITH
SEND(TxChan, text) DO
| RECEIVE(FailChan, fail) DO
| CLOSED(FailChan) DO
END
But I think CLOSE makes everything more complicated without a clear use; I mostly added it because Go has it.
EDIT2: yet another idea:
WITH
SEND(TxChan, text) DO
| ok := RECEIVE(FailChan, fail) DO
END
where ok is a BOOLEAN variable and the assignment is optional; looks a bit strange but allows the same closed handling as in Go.
Actually, I was going to suggest that if CLOSE don't fit here, maybe just omit it from your channel version.
I first came across it in the Go version of them. The libtask/libthread and Alef versions do not include it. Its absence is not really an issue, as the same information can simply be explicitly included in how one uses the channel and the data it carries.
For Go, it works well only because of the multiple return values being present in the select statement.
Between this response and https://news.ycombinator.com/item?id=38780435 it sounds like the original WITH is rather close (modulo the proposal for it to take a timeout, which could then be detected via execution of ELSE?)
Rochus, have you considered asking Dr Wirth for feedback? I haven't corresponded with him in about a decade but he was very willing to chat about tradeoffs in language features at the time.
I've updated the paper and removed the SELECT, CLOSE and CLOSED procedures as discussed.
I still think a close feature which - in contrast to Go - would just signal all waiting threads and abandon communication could be useful. I implemented this in the C library I use for experiments: https://github.com/rochus-keller/CspChan
> the proposal for it to take a timeout, which could then be detected via execution of ELSE?
It could still be added in future, e.g. as an additional parameter to SEND or RECEIVE, but in a similar way to CLOSE it makes things more complicated, and there is an alternative solution with a separate thread sending after a delay a over a channel which is received in a WITH statement where also the candidate channel waits.
> have you considered asking Dr Wirth for feedback?
I like repurposing WITH, as it already had the semantics of selecting a single branch out of an unordered collection of guarded branches (or the ELSE as complement).
I would drop the SELECT as proposed, in favour of extended WITH.
A potentially useful SELECT function (if runtime-variable communication graphs are of interest, which they may not be?) would be one which took an array of channels and directions (à la poll), or similar data structure, as a parameter.
Edit: I also suspect that the semantics of channel closure are unlikely to be as simple as you are hoping. (but maybe do something like tcp sockets, and force a consumer to drain the channel before orderly closure?)
(between array riders and lack of display access to variables in enclosing scopes, OBERON SA seems to have evolved in a direction converging upon C?)
For a similar case in Go, where I wanted a run time variable shape of graph, I was able to do without using an array. That was simply by spawning more goroutines.
So for the rx (mux) side, they all just write in to one shared chan which it then further processed. For the tx (demux) side, it is a bit simpler, but again with just an extra goroutine per tx side.
This was in a scenario where multiple clients were performing run-time registration with a mux/demux API based upon the comms needs of those specific clients.
There is the Go reflect.Select, however it is awkward, and seems to be discouraged, hence I simply used the pattern above.
> A potentially useful SELECT function would be one which took an array of channels and directions
That would actually be even a better idea than my present SELECT function, but I don't have a good solution yet for the (receive) variables. The best I can think of is just an array for the values, but that needs a lot of extra copies. I would rather prefer a built-in function than a separate module as in Go.
> OBERON SA seems to have evolved in a direction converging upon C
The underlying problem is that Oberon lacks an unordered select (WHILE ELSIF comes close but has the unfortunate —in this case, but not for many actor scenarios— repetition). Maybe something could be done with a CASE Poll(...) OF ... END?
If life were a Ponyhof, I would ask for something along the lines of:
but that'd obviously be too large a change.