Jon Lang wrote:
I'm not saying that it needs to decide whether or not you have a
halting problem; I'm saying that if there's any possibility that you
_might_ have one, you should stop looking. Let's take it as a given
that things such as exceptions, threads, and co-routines make the
automated establishment of whether or not a given function is pure a
nightmare. The easy solution for this would be to say that if a given
function makes use of exceptions, threads, or co-routines, it will not
be auto-tagged as pure. The process of auto-tagging pure functions
would not be perfect, in that there are likely to be a number of
functions that are in fact pure but don't get auto-tagged as such; but
it could still be _good_, in the sense that a useful set of pure
functions _would_ be auto-tagged.
One thing I want to make clear is that any validation of purity or auto-tagging
of said should be based on a whitelist, not on a blacklist. You have to think
of this like you think of security. Don't look for a list of things that might
give you problems, rather look to see if you have anything at all that isn't
known to be safe. A given entity (type/routine/etc) is pure iff all of its
dependencies (referenced types or operators etc) are pure. Any unknown means
unpure. To start off with, all the system-defined (defined in the Perl 6
synopsis documents) types and operators should be explicitly tagged as either
pure or impure. Then when compiling user code, we know that code is pure iff
all of its dependencies are either said system-defined pure ones or are each
other. And yes, as you said, if in doubt, mark unpure.
I would also like to disagree with the idea that using exceptions means impure;
I believe they can go together fine.
Specifically, I believe it is just fine for a pure routine to throw an
exception, though on the other hand perhaps a pure routine shouldn't be allowed
to catch an exception; a thrown exception in the pure sandbox would cause that
particular in sandbox call stack to return early with the net result being a
no-op (save thrown exception), and an impure caller routine can then catch it if
it wants.
The fact the contents of the call stack could be arbitrarily compiler reordered
doesn't change these semantics as seen outside the sandbox. In fact, one point
of a pure sandbox is that an outside caller can in some respects just see the
thing they directly call as an opaque thing which either succeeds and returns a
valid answer for its declared type or it fails with an exception.
One reason that supporting exceptions from pure routines is good is that a
runtime exception is how you handle the general case of bad input. I say the
general case because, while some things can be caught by the compiler (such as
passing a Str typed variable to something wanting an Int), some arbitrary input
constraints are too complicated and effectively become runtime checks anyway,
since even determining if you have a violation at all could require a lot of
runtime calculations and many routine calls.
There are also other exceptions you can have due to, what in the broad sense
isn't a type violation but in the strict sense is, such as numerical division.
I don't think anyone would argue that numeric division is a pure function, and
yet you can't get a normal answer if you give it a divisor of zero. Attempting
to do so, you'd probably want an exception as a result.
Now this all said, one alternative is Perl's Failure type, the unthrown
exception, which may be a way around actual exceptions. I suppose that if all
the pure routines knew how to special case an input of Failure and just abort
and return Failure themselves, we can skip an actual exception. Particularly
useful in those cases where you don't want your rocket to shutdown because one
of a hundred redundant sensors failed. Except in some ways that is a bad
example because reading from the environment like a sensor would never happen
within the pure sandbox; it would have to happen prior to entering the sandbox
and the sandbox be given a Failure input and hence abort. So in the end we
effectively still have a thrown exception. I think our code would be cleaner if
we can just use exceptions and not have to check return values.
Now, all this assumes that throwing an exception has no side-effect besides the
sandbox returning/throwing failure, and I assume an exception object is
effectively pure itself.
So, I see no problems with exceptions in a pure environment.
I also don't think threads are necessarily a problem. If all the inputs to the
pure sandbox are immutable values, and all called routines, that is the
metamodel / classes / etc, are an immutable snapshot as of the sandbox entry
time, and the pures don't see globals, then no thread can interfere with another
within the pure sandbox.
Co-routines, if they are pure routines, I don't expect to be a problem either,
but I'm less sure about that one. Actually, doesn't Haskell have co-routines,
or am I mistaken; how does Haskell do laziness relative to thoughts for Perl 6?
-- Darren Duncan