Open BryantLam opened 4 years ago
Other ideas?
The way I would resolve this problem is by creating more functions. You can:
a. Move the "Do stuff with v" lines into their own function
b. Create a function that encapsulates the lookup + error handling catch clauses you want, and call that like var v = myLookup(something)
.
you have to wrap the entire block of (possibly lots of lines of code) in a try-catch even though the failure can only occur at one point near the top.
Also, this is only the case in a non-throwing function, or in a setting where you have particular error handling in mind for certain errors, right? I.e., in many cases, simply letting the outer function be throws
is going to avoid the issue?
a. Move the "Do stuff with v" lines into their own function b. Create a function that encapsulates the lookup + error handling catch clauses you want, and call that like var v = myLookup(something).
Option b still requires that when the lookup fails, the error handling
in myLookup()
either halt or be able to return a non-nil v
anyway,
right? Otherwise the call has to be in a try
or try!
, or can't be
assigned to a non-nilable variable, and ends up looking the same.
Or maybe a different combination of a and b,
c. Encapsulate the lookup and "Do stuff with v" into a new function, and have that function use a try expression to pass errors from lookup back up?
proc DoStuffForX(x) throws {
var v: borrowed MyClass = try lookup(x);
/* Do stuff with v */
}
proc Caller() {
try {
DoStuffForX(x);
} catch {
...
}
}
Though now Caller()
can't tell if errors it catches came from the
lookup()
or the /* Do stuff */
code. And/or was the point you
wanted the /* Do stuff */
code to not be in a throws
region?
(E.g. you don't think any of it throws, and you want the compiler to tell you if you're wrong? (I guess you could put that code into another new non-throwing function.))
My feel is the "create more functions" approaches all seem like they're forcing the programmer to decompose their problem into functions not in the most natural, elegant, or performant manner, but in the manner that will appease nil checking and error handling.
Option a
seems the least onerous, but still unpleasant if the code
naturally would be something like
proc Caller(a, b, c) {
var x, y, z = things();
var v1 = lookup(x, x);
var aa = doStuff(v1, y);
var v2 = lookup(aa, z);
var bb = doStuff(v2, z);
var v3 = lookup(bb, v1);
/* do something with v1, v2, v3, a, b, and c */
That is, the "do stuff with v" lines need to repeat this pattern. So
if I follow, the aa
through "do something" lines would be moved into
a new function. But in that function, the same problem arises, and
the bb
through "do something" lines need to be moved into a further
subfunction. And that subfunction needs access to variables from
Caller
's scope, so they have to be threaded all the way through.
And notionally, the code really just wants to be one function, not three.
How about something like
var vtmp: borrowed MyClass?;
try {
vtmp = lookup(...);
} catch ... {
// error handling that halts or returns or sets vtmp some other way;
}
var v: borrowed MyClass = vtmp!;
/* Do stuff with v. */
Option b still requires that when the lookup fails, the error handling in myLookup() either halt or be able to return a non-nil v anyway, right?
I was imagining that it would create a new value return, e.g.
proc myLookup(k: K): borrowed V {
try {
return lookup(k);
} catch e:UnknownKeyError {
var dummyValue = new owned V();
store(k, dummyValue);
return lookup(k);
}
}
I don't think such logic is appropriate in all cases.
How about something like
This is pretty similar to what I was thinking; just not putting the try/catch into myLookup.
Usability problem statement for nilability. The actual problem is with localized error handling of exceptions that becomes exacerbated with nilability.
Possibly a non-issue, though it's annoying to me. If you have a function that can throw, but the point at which it throws is limited to essentially one variable assignment, you have to wrap the entire block of (possibly lots of lines of code) in a try-catch even though the failure can only occur at one point near the top.
C++ and Java have the same problem, but because nilability makes it so you can't defer initializing a non-nil-typed variable, the variable declaration has to be within the try-catch instead of outside the try-catch. In C++/Java, you can simply just move the variable declaration out of the try-catch and do the assignment from within like what I would want (I think).
Brainstorming: maybe it would be worthwhile to allow deferred initialization? I could then write code like:
Likely an intractable solution given that containers with non-nilables will be difficult to check at compile-time.
Other ideas?
(Of course, this doesn't work if the e.g.,
lookup
function returned ashared
andvar v
is still a borrow, but that becomes a lifetime issue that the user got wrong.)In any case, it might be a non-issue as something to live with for now.