wybe
wybe copied to clipboard
Allow for a failing test to automatically raise an error
If a test is called in a det context, the compilation will error. This is good, I hope we can agree.
Sometimes, though, you don't really care about what happens in the failure case, and instead would want to abort the program. Automatically generating such code would reduce boilerplate code written.
Consider the following:
?lst = [1,2,3]
if { lst^head = ?h ::
pass
| else ::
!error("no head")
}
!print(h)
In such a case, we could via some static analysis know that the head
call cannot fail. Alternatively, as the programmer, we may wish to handle this, but of course make it explicit. Consider this:
?lst = [1,2,3]
?head(lst) = ?h
!print(h)
The preceding ?
says "I know this is a test, and if this fails call !error
". The compiler could automatically generate the if
here, which would allow for the !error
call to have the call position resource set appropriately.
While this probably isn't ideal, I think it could be handy if you want to do something in an admittedly hacky way.
For this case, I would expect to write this:
?lst = [1,2,3]
head(lst) = ?h
| error "head of empty list"
I'm not sure if this works, but it should. And it does have the benefit of incorporating an error message.
I'm not crazy about overloading the question mark this way, but for cases where you don't want to bother with the error message, how about this instead:
?lst = [1,2,3]
trust {head(lst) = ?h}
I would hope that trust
could be written as a HO proc, so this feature could be in the library rather than the language.
I believe one alternative that works currently is
head(lst, ?h, _)
Which reifies the test, ignoring the succeed/fail bool
. I think the program should segfault in many cases where lst
has no head, but I'm sure there's silent failures that can arise and cause havock with other tests. :)
The ... | error
syntax looks nice enough. I think the call to error needs a !
, which is fine, we just need to double check that special resources are handled appropriately here.
trust
looks fishy to me. While I like the idea that lambdas can bind variables, I feel it's very "action-at-a-distance"-y. The lambda also need to have the {test}
modifier, I believe. Inferring such would be difficult too -- you'd need to mode check the body of the lambda, but that requires type checking of the body, which is done at the same time as mode checking of the calling context, and relies on the detism of the lambda... Sigh.
Actually, as it's a test, why can't it be reified like
def trust(test:bool) {
if { ~test :: !error("uh oh!") }
}
trust(head(lst, ?h))
# which is just
head(lst, ?h, ?tmp)
trust(tmp)
That might not work right now, but I don't see why we can't allow it.
head(lst, ?h, _)
had better not be allowed, since ?h won't be bound if the call to head
fails. If the Wybe compiler doesn't reject it, that's a bug. Anyway, I'm pretty sure LLVM will wind up rejecting it, because h
will be assigned undef
in that branch. Reifying tests with outputs can't be allowed, at least not unless the reified Boolean variable is checked and confirmed to be true
before any outputs are accessed.
You're right about the !
in front of the error call. I forgot that.
I don't see trust {head(lst) = ?h}
as action at a distance. What's necessary for this to type/mode check properly is to have the ability to declare of a lambda that it will be executed at least once (better still for this case, exactly once), and trust
would have to declare that for its argument. Of course, this would have to be checked by the compiler. Then trust {head(lst) = ?h}
is no more action at a distance than head(lst) = ?h
.
Because head(lst, ?h, _)
can't be allowed, neither can trust(head(lst, ?h))
.