til-lang / til

An easy to extend command language
56 stars 5 forks source link

Control flow - advanced mechanisms #5

Open dumblob opened 3 years ago

dumblob commented 3 years ago

All languages need some form of "escaping the structured programming fences". Older imperative languages have goto for this, but newer ones try to find some safer alternatives. Here is a (non-comprehensive) list of things to consider as it seems Til doesn't yet have a stabilized polished way to cover all the "escape structured programming use cases".

cleberzavadniak commented 3 years ago

I tend to favor Actor Model, so I believe exiting the process immediately and signaling the "linked" ones would work fine where error is not exactly applicable.

dumblob commented 3 years ago

Isn't the Actor Model rather expensive in practice (both performance-wise as well as code-bloat-wise)? If you have any specific API in mind, go ahead - I'm all ears to see and contemplate :wink:.

dumblob commented 3 years ago

If we decide to pursue something like those processors I outlined in https://github.com/til-lang/til/issues/8#issuecomment-844963543 then we might enforce every processor to get (and thus to handle) an "interrupt channel" akin to "interrupts" physical CPUs have.

It's much less intrusive than trap in shell, but encourages/forces one to handle it. We could also provide some "default" handler which the user could just stick in - we could even provide more such handlers depending which behavior we want to model - sometimes one would want to model bourne shell's trap, sometimes something like an exception from outside or even from processor's own Til code, etc.

This way it'd be easy to construct other abstractions - including effects, exceptions, traps, yields, etc.

Hm, asking myself why I didn't get this idea anytime before? Thanks for allowing me to brainstorm it here! Need to go breathe some fresh air... :wink:

cleberzavadniak commented 3 years ago

I was thinking about sending a "common" message to the calling process / spawner but that would require both handling priorities in the queue and would force the programmer to handle some exceptional behaviors together with common routines, so I thought about something more or less like error.handler - it should interrupt the normal flow but would be allowed to change values in the scope. Not sure about how "safe" that would be and that's why I would rather shove everything into receive, probably, and implement common behaviors Erlang-style, like supervisors processes.

(But I also hate the possibility of letting errors go unnoticed, so that's indeed something to think very carefully.)

dumblob commented 3 years ago

probably, and implement common behaviors Erlang-style, like supervisors processes.

This is actually the thing I'd rather get rid of (that's one of the reasons why I described "processors"). There are 2 reasons - first, the parent needs to always implement some restarting behavior which is tedious ("processors" guarantee "reentrancy" so can be spawned by a third-party in many instances if channels will get correctly muxed/demuxed) and error-prone because it makes "everything connected to everything with no overall boundaries" (it's kind of "distributed too much" :wink:). Second, it encourages thinking of processes (and thus designing the whole application as such) as something which begins here and ends there. Dot. Done. That's actually not what happens in practice - most "processes" are long-running self-contained entities.

But if the language provides only a low level primitive like a one-off process, then all the "process-instance-specific loop with select/poll/... etc." has to be implemented over and over yourself which has following downsides: slow & difficult to optimize (because programmer has too much free hands), more error prone (again, too free hands), verbose (Til shall be IMHO terser than this approach would offer).

On the other hand, I might be misunderstanding and imagining something different than you in which case I'm eager to see what you'll come up with :wink:.