Closed Technologicat closed 4 years ago
Done.
Other things to mention:
dyn
is essentially SRFI-39, using the MzScheme approach in the presence of multiple threads.~ Done.syntax.autoref
: this is essentially JavaScript's with
construct. It was removed for security reasons. Explain in the docs that it's very important that the autoref'd object comes from a trusted source, since it can hijack any name lookups.~ Done.setescape
/escape
act like Emacs Lisp's catch
/throw
.~ Done.
catch
/throw
exist also in some earlier Lisps, as well as in Common Lisp. But according to Seibel, in CL it's more idiomatic to use the lexically scoped variant BLOCK
/RETURN-FROM
.~ Done.Done.
Small issues to fix:
curry_context
means using with dyn.let(curry_context=...):
~ Done.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.
Univ
type that contains all values, is simply an embedding of untyped code into a typed environment. It does not (even attempt to) encode the latent type information.
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.
Also this information is now in doc/design-notes.md
.
More on typing:
Merged with the megapost below.
gc.get_objects()
and filter for what we want to find the instances to be updated? (Provided we still had a reference to the old class object.)Also this is now in the section on types in doc/design-notes.md
.
This is now mentioned in doc/design-notes.md
.
Clojure has (trampoline ...)
, which works pretty much exactly like our TCO setup.
The return jump(...)
solution seems to be essentially the same there (the syntax is #(...)
), but in Clojure, the trampoline must be explicitly enabled at the call site, instead of baking it into the function definition, as our decorator does.
Clojure's trampoline system is thus more explicit and simple than ours (the trampoline doesn't need to detect and strip the tail-call target's trampoline, if it has one - because with Clojure's solution, it never does), at some cost to convenience at each use site.
We now provide our own flavor of Common Lisp style conditions, see unpythonic.conditions
. It was inspired by python-cl-conditions
.
Definitely unpythonic: python-cl-conditions: Common Lisp's conditions system for Python.
Hasn't been maintained for a few years, and the banner says tests fail to build, so should probably take it for a spin first, and if it turns out to need changes, consider whether to fix it there or cook up something similar here.
It's essentially a callback system, but with callbacks implemented at and advertised by the inner level; with the outer level making the choice of which one to apply when a given condition occurs.
Outline of how it works:
_restarts
and _handlers
.with restarts
form (used at the implementing, low-level end) publishes the named restarts for the dynamic extent of the block.
RESTART-CASE
.signal
to signal a condition, without unwinding the call stack just yet.Exception
. However, condition instances are not raised, but instead passed as an argument to signal
, because we don't want to unwind the call stack when we signal.signal
looks for a handler for the given condition type and calls it normally. If there are several, the most recently bound (leftmost on the stack) handler wins.with restarts
(named call
in the example) automates the receiving end of signal
by installing an exception handler for InvokeRestart
(see below). So instead of just signal(MyCondition, ...)
, actually use call(signal, MyCondition, ...)
.signal
function could be internal since it's not intended to be used directly, and the return value of with restarts
(the only context where signal
is invoked) could be made to be used like signal(MyCondition, ...)
. Do we lose anything important with that strategy?with handle
form (used at the client, high-level end) connects handlers to condition types.
HANDLER-BIND
.invoke_restart
one of the advertised names. This is CL's INVOKE-RESTART
.InvokeRestart
exception, which causes the call stack to unwind.call
handler of the with restarts
block from inside which the signal
was sent.call
.Did some prototyping of this for unpythonic, mainly to confirm my understanding.
This has now been incorporated to the section on dyn
in doc/features.md
.
An explanation of special variables in CL can be found in Peter Seibel's book.
See especially footnote 10 in that chapter for a definition of terms. Likewise, variables in our dyn
have indefinite scope (because dyn
is implemented as a module-level global, accessible from anywhere), but dynamic extent.
So what we have in dyn
is almost exactly like CL's special variables, except we're missing convenience features such as setf
and a smart let
that auto-detects whether a variable is lexical or dynamic (if the name being bound is already in scope).
This text has been incorporated into CHANGELOG.md
. Any further updates to the 0.14.2 changelog will take place there.
"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:
unpythonic.syntax
and MacroPy.fix
: Break infinite recursion cycles (for pure functions). Based on idea and original implementation by Matthew Might and Per Vognsen.with restarts
(RESTART-CASE
), with handlers
(HANDLER-BIND
), signal
, invoke_restart
. Many convenience forms are also exported; see unpythonic.conditions
for a full list. For an introduction to conditions, see Chapter 19 in Practical Common Lisp by Peter Seibel.fixpoint
: Arithmetic fixed-point finder (not to be confused with fix
).within
: Yield items from iterable until successive iterates are close enough (useful with Cauchy sequences).chunked
: Split an iterable into constant-length chunks.lastn
: Yield the last n
items from an iterable.pad
: Extend iterable to length n
with a fillvalue
.interleave
: For example, interleave(['a', 'b', 'c'], ['+', '*']) --> ['a', '+', 'b', '*', 'c']
. Interleave items from several iterables, slightly differently from zip
.CountingIterator
: Count how many items have been yielded, as a side effect.slurp
: Extract all items from a queue.Queue
(until it is empty) into a list, returning that list.map
: Curry-friendly thin wrapper for the builtin map
, making it mandatory to specify at least one iterable.ulp
: Given a float x
, return the value of the unit in the last place (the "least significant bit"). At x = 1.0
, this is the machine epsilon, by definition of the machine epsilon.dyn
now supports rebinding, using the assignment syntax dyn.x = 42
. For an atomic mass update, see dyn.update
.box
now supports .set(newvalue)
to rebind (returns the new value as a convenience), and unbox(b)
to extract contents. Syntactic sugar for rebinding is b << newvalue
(where b
is a box).islice
now supports negative start and stop. (Caution: no negative step; and it must consume the whole iterable to determine where it ends, if at all.)Fixed:
lazyutil
if MacroPy is not installed.identity
and const
with zero args (#7).Merged with the megapost below.
The second famous killer feature of CL - connecting to a running Lisp app and monkey-patching it live - is powered by Swank, the server component of SLIME. See [0], [1], [2] and [3].
For a Swank server for Python, see [4]. For one for Racket, see [5].
As [3] says, if you intend to monkey-patch a running loop, that only works if the loop is in FP style, using recursion... since then overwriting the top-level name it's calling to perform the recursion will make new iterations of the loop use the updated code. This requires the implementation to have TCO. (In unpythonic
, this setup is possible with @trampolined
.)
Of course, we don't have anything like SBCL's #'save-lisp-and-die
, or indeed the difference between defvar
(init only if it does not already exist) and defparameter
(always init) (for details, see Chapter 6 in Peter Seibel's Practical Common Lisp). Python wasn't really designed, as a language, for the style of development where an image is kept running for years and hot-patched as necessary.
But there's ZODB [1] [2] [3] for persistent storage in Python. It can semi-transparently store and retrieve any Python object that subclasses persistent.Persistent
; haven't tried that class as a mixin, though (would be useful for persisting unpythonic
containers box
, cons
, and frozendict
).
Here persistent means the data lives on disk; not to be confused with the other sense of "persistent data structures", as in immutable ones, as in pyrsistent.
Only semi-transparently, because you have to assign the object into the DB instance to track it, and transaction.commit()
to apply pending changes. Explicit is better than implicit.
But any data stored under the DB root dict (recursively) is saved. So it's a bit like our dyn
, a special place into which you can store attributes, and which plays by its own rules.
pickle
under the hood, so functions are always loaded from their most recent definitions on disk.pickle
on ACID: atomicity, consistency, isolation, durability.Possibly useful ZODB trivia:
dbroot['x'] = x
and dbroot.x = x
do the same thing; the second way is modern style. The old way is useful mainly when the key is not a valid Python identifier.BTrees
in ZODB._v_
are volatile, i.e. not saved. They may vanish between any two method invocations if the object instance is in the saved state, because ZODB may choose to unload saved instances at any time to conserve memory._p_
are reserved for ZODB. Set x._p_changed = True
to force ZODB to consider an object instance x
as modified.x.foo
is a builtin list
that was mutated by calling its methods. Otherwise ZODB won't know that x
has changed when we x.foo.append("bar")
. Another way to signal the change to ZODB is to rebind the attribute, x.foo = x.foo
, with the usual consequences.Persistent
, it's not allowed to later change your mind on this (i.e. make it non-persistent), if you want the storage file to remain compatible. See https://github.com/zopefoundation/ZODB/issues/99__main__
, because the module name must remain the same when the data is loaded back (as mentioned in the tutorial).The third killer feature of Common Lisp, compiling to machine code that rivals C++ in performance, is unfortunately not available for Python at this time. :)
TAGBODY
/GO
in Python.~ Tracked in #45.Merged with reading links below.
Just archiving some links to reading...
doc/design-notes.md
. In the Pyramid web framework documentation:unpythonic
, e.g. dynassign
only sets its own state, so it should be safe. But regutil.register_decorator
is potentially dangerous, specifically in that if the same module is executed once as __main__
(running as the main app) and once as itself (due to also getting imported from another module), a decorator may be registered twice. (It doesn't cause any ill effects, though, except for a minor slowdown, and the list of all registered decorators not looking as clean as it could.)ast
module, so no MacroPy.The 0.14.2 documentation update megapost - Updated 7 Aug, 2020
Remaining documentation TODOs for 0.14.2. Done and cancelled ones struck out.
fploop
is TCO'd, so you can run arbitrarily long loops with it. Otherwise the feature would hardly be worth a README mention.~ Done.memoize
caches exceptions too.~ Done.HACKING.md
).~ Rough first cut done.unpythonic
is intended as a serious tool for productivity as well as for teaching (language concepts from outside the Python community, as well as metaprogramming techniques by example), right now work priorities mean that it's developed and maintained on whatever time I can spare for it.~unpythonic.symbol.sym
and unpythonic.symbol.gensym
.~sym
is the interned symbol type, like Lisp's symbols. The name determines object identity.~gensym
makes new, unique uninterned symbols (class: gsym
), with a pseudo-random UUID and a human-readable label. The UUID determines object identity. Like Lisp's gensym
and JavaScript's Symbol
.~
pickle
roundtrip. Instantiation is thread-safe.~sym
and gensym
, and unit tests in unpythonic/test/test_symbol.py
for usage examples.~unpythonic.singleton.Singleton
, a singleton abstraction with thread-safe instantiation that interacts properly with pickle
.~doc/repl.md
and imacropy
docs) the selling points of imacropy.console.MacroConsole
:~ Done in doc/repl.md
. Now just the imacropy
docs. Done too.
from somemod import macros, ...
, the console automatically first reloads somemod
, so that a macro import always sees the latest definitions.~__doc__
.~from unpythonic.syntax import macros, let
, would not define the name let
at runtime. Now it does, with the name pointing to the macro stub.~cerror
, when a handler invokes its proceed
restart, is a bit like contextlib.suppress
, except it continues right from the next statement.~ Documented.dyn
.~box
. Show where it's needed - i.e. as a semantically explicit replacement for the single-item list hack, when a function needs to rebind its argument in such a way that the rebinding takes effect also in the caller (and cannot simply use nonlocal
or global
due to the original name not being in lexical scope).~ Done.
f
and g
, where f
calls g
, and g
wants to have the side effect of effectively rebinding a local variable of f
...~box
is the solution.~box
: deprecate accessing .x
directly. Doesn't work with the new ThreadLocalBox
. Now we have .get()
to retrieve the value in an OOP way (though unbox(b)
instead of b.get()
is the recommended API).~ Done.ThreadLocalBox
. Like box
, but the contents are thread-local.~ Done.Shim
. A Shim
holds a box
(or a ThreadLocalBox
), and redirects attribute accesses on the shim to whatever object happens to currently be in the box. (E.g. this can combo with ThreadLocalBox
to redirect stdin/stdout only in particular threads. Put the stream object in a ThreadLocalBox
, then shim that, then assign the shim to replace sys.stdin
...)~ Done.async_raise
. It can be used to inject asynchronous exceptions such as KeyboardInterrupt
into another thread.~ Done.
AUTHORS.md
.)~ Documented.ctypes.pythonapi
, which allows access to Python's C API from within Python. (If you think ctypes.pythonapi
is too quirky, the pycapi
PyPI package smooths over the rough edges.) Combining the two gives async_raise
without the need for a C extension. Unfortunately PyPy doesn't currently (Jan 2020) implement this function in its CPython C API emulation layer, cpyext
.~ Documented.KeyboardInterrupt
in a remote REPL session (inside the session thread, which can be, at that time, stuck waiting in interact()
), when the user presses Ctrl+C at the client side. This and similar awkward situations in network programming are pretty much the only legitimate use case for this.~ Documented.unpythonic.net.PTYSocketProxy
. This plugs a PTY between some Python code that expects to run in a terminal, and a network socket. Unlike many examples on the internet, this one doesn't use pty.spawn
, so the slave doesn't need to be a separate process.~ Done.unpythonic.net.server
and unpythonic.net.client
, which are unpythonic
's way to allow the user to connect to a running Python process and modify its state (à la Swank in Common Lisp).~ Done, doc/repl.md
.
importlib.reload
, it's possible to tell the running process to reload arbitrary modules from disk. But if someone has from-imported anything from them, tough luck - the from-import will refer to the old version, unless you reload the module that did the from-import, too. (Good luck catching all of them.)~ Documented.save-lisp-and-die
, so it's not like "Lisp image based programming" as in CL. If you need to restart, you need to restart normally. So in Python, never just hot-patch; always change your definitions on disk, so your program will run with the new definitions the next time it's cold-booted. Once you're done testing, then reload those definitions in the live process, if you need/want to.~ Documented.continuations
has some limitations, and in its present state, is useful mainly for teaching continuations in a Python setting.~ Documented.
try
/finally
is fine. Context management with with
blocks is fine, too.)~with continuations
block will be caught in such a block, or outside.)~unpythonic
constructs, even when those constructs are functions. This has always been the case, but I've forgotten to mention it in the docs. (All the code examples say it between the lines, kind of - they only use from-imports.)~ Documented at the start of doc/macros.md
.
curry
to refer to functions imported from unpythonic
. A full list would be useful here, but to produce that I may have to dig through 2k lines of macro code (excluding comments, docstrings and blank lines). And flag the relevant places to easily find them again later.~ Mentioned curry
specifically, but let's build the full list later. It's a lot of code to dig through - manually, because there is no unifying factor to grep for.@looped_over
, mention that any unpacking of the input item must be performed manually, because tuple parameter unpacking was removed from the language in Python 3.0. See PEP 3113.~ Documented.
def
or a lambda
cannot, making the language more irregular than it needs to be (both for
and def
/lambda
create bindings). I suppose this is an instance of practicality beats purity.~fix
. Add a new subsection after the one on curry
and reduction rules.~ Done in f73959e.namelambda
to demo.~ Done.looped
to demo.~ Done.numerics.py
example is up to date, update if necessary.~ Added some comments for other ways to do certain things.ll(...)
plays the role of a linked list display.~lazyrec
, see the lazyrec[]
syntax transformer and the function is_literal_container
in unpythonic.syntax.lazify
.do[]
(extra bracket syntax), see the syntax transformer implicit_do
in unpythonic.syntax.letdo
.yield
and yield from
in a genexpr, see §6.2.8. If our usage examples or unit tests have any, fix them.~ Checked, everything is peachy.env
is a bit like types.SimpleNamespace
, but with additional features. Mention this.~ Done.doc/readings.md
.
async
/await
.~
async
/await
work pretty much just like in Python.~async
/await
, but it has futures with true parallelism. The shift
/reset
delimited continuation operators can be used to implement something akin to async
/await
.~gc.get_objects()
and filter for what we want to find the instances to be updated? (Provided we still had a reference to the old class object.)~doc/design-notes.md
.unpythonic.net.server
and unpythonic.net.client
for hot-patching. It doesn't talk with SLIME, but perhaps Python doesn't need to. The important point (and indeed the stuff of legends) is being able to connect to a running process and change things as needed. Being able to do that is an obvious expected feature of any serious dynamic language. (Indeed both Common Lisp and Python can do it, with the appropriate infrastructure as a library.)~unpythonic
, this setup is possible with @trampolined
.)~ Documented.#'save-lisp-and-die
, or indeed the difference between defvar
(init only if it does not already exist) and defparameter
(always init) (for details, see Chapter 6 in Peter Seibel's Practical Common Lisp). Python wasn't really designed, as a language, for the style of development where an image is kept running for years and hot-patched as necessary.~ Documented.persistent.Persistent
; haven't tried that class as a mixin, though (would be useful for persisting unpythonic
containers box
, cons
, and frozendict
).~ Documented in doc/repl.md
.transaction.commit()
to apply pending changes. Explicit is better than implicit.~dyn
, a special place into which you can store attributes, and which plays by its own rules.~
pickle
under the hood, so functions are always loaded from their most recent definitions on disk.~pickle
on ACID: atomicity, consistency, isolation, durability.~dbroot['x'] = x
and dbroot.x = x
do the same thing; the second way is modern style. The old way is useful mainly when the key is not a valid Python identifier.~BTrees
in ZODB.~_v_
are volatile, i.e. not saved. They may vanish between any two method invocations if the object instance is in the saved state, because ZODB may choose to unload saved instances at any time to conserve memory.~_p_
are reserved for ZODB. Set x._p_changed = True
to force ZODB to consider an object instance x
as modified.~x.foo
is a builtin list
that was mutated by calling its methods. Otherwise ZODB won't know that x
has changed when we x.foo.append("bar")
. Another way to signal the change to ZODB is to rebind the attribute, x.foo = x.foo
, with the usual consequences.~Persistent
, it's not allowed to later change your mind on this (i.e. make it non-persistent), if you want the storage file to remain compatible. See https://github.com/zopefoundation/ZODB/issues/99~__main__
, because the module name must remain the same when the data is loaded back (as mentioned in the tutorial).~doc/design-notes.md
as Common Lisp, Python and productivity.~ Done, and slightly updated.
MacroPy
introduced syntactic macros to Python (in 2013, according to the git log). It wasn't bad as a Lisp replacement even back in 2000 - see Peter Norvig's essay Python for Lisp Programmers. Some more historical background, specifically on lexically scoped closures, in PEP 3104, PEP 227, and Historical problems with closures in JavaScript and Python.~MacroPy
, unpythonic
and pydialect
are trying to push the language-level features a further 5%. (A full 100% is likely impossible when extending an existing language; if nothing else, there will be seams.)~memoize
to doc/features.md
, oddly it's missing one.~ Done.CHANGELOG.md
, so we only need to check that features.md
includes a mention and example of each.~with
protocol.~
CHANGELOG.md
is up to date.~
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.~
Updated 17 Mar 2020
Links to relevant readings. ~Will likely~ Has become doc/readings.md
. Any further updates will take place there.
new
with the appropriate classes from types
).Joel Spolsky, 2000: Things you should never do, part I
"We’re programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We’re not excited by incremental renovation: tinkering, improving, planting flower beds.
There’s a subtle reason that programmers always want to throw away the code and start over. The reason is that they think the old code is a mess. And here is the interesting observation: they are probably wrong. The reason that they think the old code is a mess is because of a cardinal, fundamental law of programming:
It’s harder to read code than to write it."
doc/design-notes.md
. In the Pyramid web framework documentation:
unpythonic
, e.g. dynassign
only sets its own state, so it should be safe. But regutil.register_decorator
is potentially dangerous, specifically in that if the same module is executed once as __main__
(running as the main app) and once as itself (due to also getting imported from another module), a decorator may be registered twice. (It doesn't cause any ill effects, though, except for a minor slowdown, and the list of all registered decorators not looking as clean as it could.)ast
module, so no MacroPy.Finally, everything necessary is done. Closing.
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.