The Need To Rethink ERROR!

Thoughts on CALL And Errors

In @gchiu's small pharmac script, there are a couple of uses of CALL that run ghostscript (gs):

call [
    gs -sDEVICE=pngmono -o (join root "-%02d.png") -r600 (pdfname)
]

call [
    gs -sDEVICE=eps2write -sPAPERSIZE=a4
        -o (join root "-%02d.eps") (pdfname)
]

But if either of these calls fail, the script will keep running. That is because CALL is not checking the result code...and in OS terms, any process that returns a nonzero value had an error.

I think the default behavior of CALL should be to raise an error on a nonzero result.

The best strategy seems that if you request the exit status explicitly, then it would assume you were going to handle it.

[# status]: call [...]

What makes this a little bit weird is that there doesn't seem to be any particularly great return result from CALL besides the integer return value. So there'd be a ~none~ return result by default, and this status would be the secondary result.

This doesn't exactly fit the pattern of my "distinguished error" result, because the status is an integer... not an ERROR!. Though if you don't ask for the status and it raises an error because of that you could see it that way:

[#].err: call [...]

Could a Function Tell If You "Use" Its Primary Return Result?

It's hard to say what "using" would mean.

For instance, imagine this scenario:

>> call [rm nonexistentfile.txt] print "nocheck"
** Error: CALL returned exitstatus 1
** Near: rm nonexistentfile.txt

So I'm proposing that would raise an error in a script. But what if you wrote:

>> call [rm nonexistentfile.txt]
== 1

The value 1 got "used" in the sense that it was reported by the console. Should that count as suppressing the error, since someone took the result?

What about:

>> elide call [rm nonexistentfile.txt]

The ELIDE consumed the result, but threw it away. It doesn't seem that should suppress errors.

Really only multiple-returns are set up to be explicitly thought of as "requested" vs. not. We could argue that explicit setting to a word is different:

status: call [rm nonexistentfile.txt]

And then CALL could be told whether there was an explicit assignment or not. But the way that multi-returns are checked is if their argument names are null or not...and the RETURN argument isn't named right now (it's just the RETURN: in the spec).

Overall I am wary of trying to do anything detection-wise with the main return, and keep that for multi-returns...which are really just syntactic sugar over refinements. That has this weird implication for CALL not having the exitstatus as its primary return. Oh well.

Thoughts on CALL and Asynchronousness

In the model I'm pushing for asynchronousness, this argument isn't as applicable...because the error can be received by the callsite.

What you'd want to do to make CALL asynchronous would be to just spawn it off on a separate "goroutine"-like thing.

If you didn't care about the result, you could just do something like this:

 go [call [echo "I'm being printed by the OS"]]
 print "This could print before the echo executes."

But if you wanted to coordinate, you'd make something like a "channel" and have the goroutine transmit updates on it.

let c: make-channel
go [
    send-channel c <start>
    call [echo "I'm being printed by the OS"]
    send-channel c <finish>
    close-channel c
]
while [msg: receive-channel c] [
   print ["Got signal from channel:" msg]
]
print "Channel has been closed"

That would be one strategy. But a different strategy would be used if you wanted to communicate with the input and receive the output, in a streaming fashion.

2 Likes