Technologicat / unpythonic

Supercharge your Python with parts of Lisp and Haskell.
Other
91 stars 3 forks source link

Update docs for 0.14.2 #8

Closed Technologicat closed 4 years ago

Technologicat commented 5 years ago

Document the new features in README. Mention bug fixes in changelog. See recent commits for details.

This part done. See below for any remaining points, especially the megapost and readings.

Technologicat commented 5 years ago

Done.


Other things to mention:

Technologicat commented 5 years ago

Done.


Small issues to fix:

Technologicat commented 5 years ago

A section on types has been added to doc/design-notes.md.


Maybe add a note about type systems somewhere in the README. This three-part discussion on LtU was particularly interesting.

Taking this into a Python context, if explicit is better than implicit (ZoP §2), why not make at least some of this latent information, that must be there anyway, machine-checkable? Hence type annotations (PEP 3107, 484, 526) and mypy.

Unpythonic itself will likely remain untyped indefinitely, since I don't want to enter that particular marshland with things like curry and with continuations. It may be possible to gradually type some carefully selected parts - but that's currently not on the roadmap.

Technologicat commented 5 years ago

Also this information is now in doc/design-notes.md.


More on typing:

Technologicat commented 5 years ago

Merged with the megapost below.


Technologicat commented 5 years ago

Also this is now in the section on types in doc/design-notes.md.


Technologicat commented 5 years ago

This is now mentioned in doc/design-notes.md.


Technologicat commented 5 years ago

We now provide our own flavor of Common Lisp style conditions, see unpythonic.conditions. It was inspired by python-cl-conditions.


Technologicat commented 5 years ago
Technologicat commented 5 years ago

This has now been incorporated to the section on dyn in doc/features.md.


Technologicat commented 5 years ago

This text has been incorporated into CHANGELOG.md. Any further updates to the 0.14.2 changelog will take place there.


Changelog for 0.14.2 (draft; updated Nov 4, 2019)

"Greenspun" edition:

I think that with the arrival of conditions and restarts, it is now fair to say unpythonic contains an ad-hoc, informally-specified, slow implementation of half of Common Lisp. To avoid bug-ridden, we have tests - but it's not entirely impossible for some to have slipped through.

This release welcomes the first external contribution. Thanks to @aisha-w for the much improved organization and presentation of the documentation!

Language version:

Rumors of the demise of Python 3.4 support are exaggerated. While the testing of unpythonic has moved to 3.6, there neither is nor will there be any active effort to intentionally drop 3.4 support until unpythonic reaches 0.15.0.

That is, support for 3.4 will likely be dropped with the arrival of the next batch of breaking changes. The current plan is visible in the roadmap as the 0.15.0 milestone.

If you still use 3.4 and find something in unpythonic doesn't work there, please file an issue.

New:

Fixed:

Technologicat commented 5 years ago

Merged with the megapost below.


Technologicat commented 5 years ago
Technologicat commented 5 years ago

Merged with reading links below.


Just archiving some links to reading...

Technologicat commented 5 years ago

The 0.14.2 documentation update megapost - Updated 7 Aug, 2020

Remaining documentation TODOs for 0.14.2. Done and cancelled ones struck out.

Technologicat commented 5 years ago

An updated version of this text has been incorporated to doc/features.md.


Condition system - draft:

Following Peter Seibel (Practical Common Lisp, chapter 19), we define errors as the consequences of Murphy's Law. As we already know, an exception system splits error-recovery responsibilities into two parts. In Python terms, we speak of raising and then handling an exception. The main difference is that a condition system, instead, splits error-recovery responsibilities into three parts: signaling, handling and restarting.

Why would we want to do that? The answer is improved modularity. Consider separation of mechanism and policy. We can place the actual error-recovery code (the mechanism) in restarts, at the inner level (of the call stack) - which has access to all the low-level technical details that are needed to actually perform the recovery. We can provide several different canned recovery strategies - generally any appropriate ways to recover, in the context of each low- or middle-level function - and defer the decision of which one to use (the policy), to an outer level. The outer level knows the big picture - why the inner levels are running in this particular case, i.e. what we are trying to accomplish and how. Hence, it is in the ideal position to choose which error-recovery strategy is appropriate in that context.

Fundamental signaling protocol

Generally a condition system operates as follows. A signal is sent (outward on the call stack) from the actual location where the error was detected. A handler at any outer level may then respond to it, and execution resumes from the restart invoked by the handler.

This sequence of catching a signal and invoking a restart is termed handling the signal. Handlers are searched in order from innermost to outermost on the call stack.

In general, it is allowed for a handler to fall through (return normally); then the next outer handler for the same signal type gets control. This allows chaining handlers to obtain their side effects (such as logging). This is occasionally referred to as canceling, since as a result, the signal remains unhandled.

Viewed with respect to the call stack, the restarts live between the (outer) level of the handler, and the (inner) level where the signal was sent from. When a restart is invoked, the call stack unwinds only partly. Only the part between the location that sent the signal, and the invoked restart, is unwound.

High-level signaling protocols

We actually provide four signaling protocols: signal (i.e. the fundamental protocol), and three that build additional behavior on top of it: error, cerror and warn.

If no handler handles the signal, the signal(...) protocol just returns normally. In effect, with respect to control flow, unhandled signals are ignored by this protocol. (But any side effects of handlers that caught the signal but did not invoke a restart, still take place.)

The error(...) protocol first delegates to signal, and if the signal was not handled by any handler, then raises ControlError as a regular exception. (Note the Common Lisp ERROR function would at this point drop you into the debugger.) The implementation of error itself is the only place in the condition system that raises an exception for the end user; everything else (including any error situations) uses the signaling mechanism.

The cerror(...) protocol likewise makes handling the signal mandatory, but allows the handler to optionally ignore the error (sort of like ON ERROR RESUME NEXT in some 1980s BASIC variants). To do this, invoke the proceed restart in your handler; this makes the cerror(...) call return normally. If no handler handles the cerror, it then behaves like error.

Finally, there is the warn(...) protocol, which is just a lispy interface to Python's warnings.warn. It comes with a muffle restart that can be invoked by a handler to skip emitting a particular warning. Muffling a warning prevents its emission altogether, before it even hits Python's warnings filter.

If the standard protocols don't cover what you need, you can also build your own high-level protocols on top of signal. See the source code of error, cerror and warn for examples (it's just a few lines in each case).

Notes

The name cerror stands for correctable error, see e.g. CERROR in the CL HyperSpec. What we call proceed, Common Lisp calls CONTINUE; the name is different because in Python the function naming convention is lowercase, and continue is a reserved word.

If you really want to emulate ON ERROR RESUME NEXT, just use Exception (or Condition) as the condition type for your handler, and all cerror calls within the block will return normally, provided that no other handler handles those conditions first.

Conditions vs. exceptions

Using the condition system essentially requires eschewing exceptions, using only restarts and handlers instead. A regular exception will fly past a with handlers form uncaught. The form just maintains a stack of functions; it does not establish an exception handler. Similarly, a try/except cannot catch a signal, because no exception is raised yet at handler lookup time. Delaying the stack unwind, to achieve the three-way split of responsibilities, is the whole point of the condition system. Which system to use is a design decision that must be made consistently on a per-project basis.

Be aware that error-recovery code in a Lisp-style signal handler is of a very different nature compared to error-recovery code in an exception handler. A signal handler usually only chooses a restart and invokes it; as was explained above, the code that actually performs the error recovery (i.e. the restart) lives further in on the call stack. An exception handler, on the other hand, must respond by directly performing error recovery right where it is, without any help from inner levels - because the stack has already unwound when the exception handler gets control.

Hence, the two systems are intentionally kept separate. The language discontinuity is unfortunate, but inevitable when conditions are added to a language where an error recovery culture based on the exception model (of the regular non-resumable kind) already exists.

CAUTION: Make sure to never catch the internal InvokeRestart exception (with an exception handler), as the condition system uses it to perform restarts. Again, do not use catch-all except clauses!

If a handler attempts to invoke a nonexistent restart (or one that is not in the current dynamic extent), ControlError is signaled using error(...). The error message in the exception instance will have the details.

If this ControlError signal is not handled, a ControlError will then be raised as a regular exception; see error. It is allowed to catch ControlError with an exception handler.


That should be most of the narrative. Still need to include API docs.

Relevant LtU discussion.

Stroustrup on why he chose in C++ not to have a resume facility.


~Harebrained idea: could we mix and match - make with handlers catch exceptions too? It may be possible to establish a handler there, since the only exception type raised by the condition system is InvokeRestart (and, well, ControlError, if something goes horribly wrong).~

~OTOH, not sure if that would be useful. The style of code that goes into a Lisp-style signal handler is different from what would go into an exception handler. A signal handler usually only chooses a restart and invokes it. The actual restart code lives (dynamically) further in. An exception handler, on the other hand, must respond by directly performing its "restart action" further out on the call stack, because the stack has already unwound when the handler gets control. Maybe it's better to keep the systems separate.~

Technologicat commented 4 years ago

Updated 17 Mar 2020

Links to relevant readings. ~Will likely~ Has become doc/readings.md. Any further updates will take place there.


Technologicat commented 4 years ago

Finally, everything necessary is done. Closing.