# New Ticket Created by  Brandon Allbery 
# Please include the string:  [perl #132185]
# in the subject line of all future correspondence about this issue. 
# <URL: https://rt.perl.org/Ticket/Display.html?id=132185 >


This turns out to be fairly complex, and has implications that may go well
beyond file tests. (Again! It only caused a syntax rethink and the redesign
of smartmatching when I poked file test issues in Pugs in 2007....)

The original problem is that a fairly obvious (from shells or perl 5 or
etc.) test for whether a file (as such) exists or not, can yield surprises:

pyanfar Z$ 6 '".profileX".IO.f.say'

Failed to find '/home/allbery/.profileX' while trying to do '.f'
  in block <unit> at -e line 1

Naïvely, I expect this to output False, not throw.

The reason for this is fairly obvious: if I use it in a Bool context then
the Failure gets coerced as I expect, But if I'm not aware that this relies
on Failure getting disarmed when coerced to Bool, using it with something
that accepts Any (like say) will throw instead of giving me False.
Accordingly, it works as expected if I force coercion to Bool.

pyanfar Z$ 6 'say ?".profileX".IO.f'
False

So, the first problem is that you have to be aware of the special behavior
of Failure and how it interacts with a method which is documented as
producing Bool.

If it stopped there, this might not even be worth a bug report except
possibly for documentation. But if you dig a little farther, things start
getting more complex:

pyanfar Z$ 6 '".profileX".IO.e.say'
False

The .e method behaves differently, and how I expected .f to behave!

Again, there is a rational explanation: it is, logically, a different
operation. In lower level terms, .e just checks whether stat() succeeded,
whereas .f needs to also look at the result and gives me Failure if the
stat() failed. But you need to know that this difference exists, because
it's not immediately clear from the documentation.

Perl 5 had a variant of this, and "leaked" a hint of it with its magic
_ parameter.
As a way of exposing the difference between just calling stat() and using
its result, though, its kinda the worst of all possible worlds. (Not to
mention the questions of thread safety, etc. that come up when you start
tossing such magic around.)

Things get deeper yet, though. Which kinds of failures of stat() result in
Failure, and which if any produce harder exceptions? An EIO return from
stat() is a much more fundamental failure than an ENOENT return. (tl;dr: EIO
means the filesystem is hosed. For a remote filesystem it may mean the
connection to the server has been lost; for a local one, it could mean
someone unplugged the USB hard drive or it could mean you need to
immediately shut down, fsck, and possibly dig out the backups. In all
cases, it's a deeper issue than a file simply not existing.) Is this a
situation where we might actually want a harder kind of Failure that
doesn't get disarmed on coercion to Bool, but does if tested with .defined?
Or does this justify a hard exception? And, there are likely to be
intermediate cases where the right answer is even less clear.

If you go back and look at the difference between .e and .f, you also get
other questions. Notably, if you decide that .f should behave like .e, do
you do this explicitly (and for each operation), or do you arrange for it
to be part of the signature, or do you perhaps handle any Failure return
through a declared Bool return type by coercing it to Bool? All of these
answers are unappealing, some moreso than others (unconditional coercion
might actually be right in the general case, but it scares me --- and
interacts strongly with the preceding question).

Making the return type of the file tests a coercion type to express the
notion that, here, Failure should coerce to Bool (but maybe not always?
gain see previous section) is tempting, but (a) I have no idea what the
syntax would be (b) currently that information is ignored, or possibly
throws at compile time (c) and the existing coercion machinery operates
inbound to a function, not outbound for its result. And is this situation
actually common enough to justify such a mechanism, especially considering
that it probably makes a relatively hot path more expensive?

So there's actually a fair amount to think about here. And, depending on
your early answers, this could potentially be three tickets or maybe even
more.

-- 
brandon s allbery kf8nh                               sine nomine associates
allber...@gmail.com                                  ballb...@sinenomine.net
unix, openafs, kerberos, infrastructure, xmonad        http://sinenomine.net

Reply via email to