"errsafe returning the exception (rather than nil)"
I actually prefer nil. When I use 'errsafe, it's usually in a boolean context like (unless (errafe ...) ...).
---
"Don't care (at least, in this particular program)."
That's just fine. ^_^
"If you did care, you could pass a function as the second argument to errsafe, and use that to do whatever it is you want to do."
I don't really follow, 'cause I don't know your code well enough to know which usage of 'errsafe you're talking about, but no worries.
---
By the way, I do like passing a "default" behavior to 'errsafe. I've often considered doing the same thing myself, but with a somewhat different interface:
(errsafe a x y z)
-->
(on-err (fn (it) x y z) (fn () a)) ; where 'it is anaphoric
This way, if I say (errsafe:err "hey"), I get nil as usual, but if I say (errsafe (err "hey") it), then I get the exception.
Here's another alternative I've thought about, which is a lot like what you're saying with "ala Python":
(errsafe a x y z)
-->
(on-err (fn (it) (if x y z)) (fn () a))
This way, (errsafe:err "hey") and (errsafe (err "hey") it) work exactly the same way, but we can also easily simulate a series of catch/except blocks:
(errsafe)
-->
nil
(errsafe a x y z)
-->
(on-err (fn (it) (errsafe x y z)) (fn () a))
This way 'errsafe works a lot like 'or, trying several expressions one after another. I don't think I'd ever use this, though. ^_^
---
"Huh?"
By "encoding the mismatch in the pattern's return value," I mean having it return a certain kind of value on a match and another kind of value on a mismatch. For instance, oftentimes I'll return nil on failure and a singleton list on success, so that I can use the idioms (iflet (x) (foo a b c) ...) and (car:foo a b c). However, nil doesn't give much information about the failure, so occasionally I'll return (list t <result>) and (list nil <failure-message>) or (list 'success <result>) and (list 'failure <failure-message>).
"I actually prefer nil. When I use 'errsafe, it's usually in a boolean context like (unless (errafe ...) ...)."
(unless (errsafe ... [nil]) ...)
Voila.
---
"I don't really follow, 'cause I don't know your code well enough to know which usage of 'errsafe you're talking about, but no worries."
Right now, you pass 2 functions to on-err, right? The first function receives the exception, if there is any. So what I'm saying is, we could change errsafe so it accepts a second argument, which would behave like the first argument to on-err:
(errsafe ... (fn (x) ...)) ; the variable x is the exception, if one is thrown
Which is somewhat nicer than doing the same thing with on-err:
(on-err (fn (x) ...)
(fn () ...))
---
"This way, if I say (errsafe:err "hey"), I get nil as usual, but if I say (errsafe (err "hey") it), then I get the exception."
Sounds neat.
---
"This way 'errsafe works a lot like 'or, trying several expressions one after another. I don't think I'd ever use this, though. ^_^"
Also sounds neat.
---
"For instance, oftentimes I'll return nil on failure and a singleton list on success, so that I can use the idioms (iflet (x) (foo a b c) ...) and (car:foo a b c). However, nil doesn't give much information about the failure, so occasionally I'll return (list t <result>) and (list nil <failure-message>) or (list 'success <result>) and (list 'failure <failure-message>)."
Sounds really ad-hoc. :P If I'm understanding correctly, that sounds like a manual version of failcall.
Oh yeah, there's something I think would be even more useful than any of these 'errsafe variants:
(ifreturn result exn (foo)
do-something-with.result
an-error-we-expect.exn
(do prn.exn (do-something-else))
an-error-we-dont-want-to-propagate.exn
(err "some better error")
raise.exn)
(aifreturn (foo)
do-something-with.it
an-error-we-expect.it
(do prn.it (do-something-else))
an-error-we-dont-want-to-propagate.it
(err "some better error")
raise.it)
With a traditional try block, if I want to 'do-something-with the return value only if there isn't an error, the obvious place to do it is right there inside the try block, after the value has been successfully calculated. However, that means the errors thrown by 'do-something-with itself can be caught, which is almost never my intention.
Instead, I go to great lengths with booleans just to avoid doing putting too much inside the try block. What I really want are macros like these.
Meanwhile, the same idea could be beneficial for fixed-syntax languages too:
try:
foo()
then result:
do_something_with(result)
except Exception as e
do_something_else_with(e)
try
{
foo();
}
then ( Foo result )
{
doSomethingWith( result );
}
catch ( Exception e )
{
doSomethingElseWith( e );
}