Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Advice on handling multiple return values?

2 views
Skip to first unread message

Elena

unread,
Jul 2, 2009, 12:20:37 PM7/2/09
to
Hello,

I'm wrapping some C functions using FFI and I'd like to know how would
you deal with functions which either fail or return more than one
value. Currently I'm using "values" and "let-values" like this:

(define (foo)
...
(values error-code result))

(let-values (((error-code result) (foo)))
;; Here, if "error-code" means success, then evaluate "result",
otherwise handle the error
...)

I've read that some people are not fond of multiple return values...
Would you suggest a different strategy? Preferably not involving
exceptions, since I'd prefer an if-then strategy.

Thanks

Aaron W. Hsu

unread,
Jul 2, 2009, 1:59:40 PM7/2/09
to

I like multiple return values, so let's get that out of the way right now.
However, I don't see here why you use multiple return values. An error
situation is an exceptional situation, and one where you would usually not
care to have a "result," since presumably the error occured while trying
to do something to get that result. Instead, the common idiom here is to
either signal an error in some manner using 'error' or the like (which can
also be done in an FFI), or to return some value which indicates that the
computation doesn't have a result in the normal sense. What Scheme
implementation are you using? If you are using one with an exception and
condition based error system, you might do something like:

(with-exception-handler handle-error foo)

Where 'foo' might then be defined as:

(define foo
(lambda ()
... val ; Normal code path
... (error 'foo "Problem!") ; Exceptional path
))

Most Scheme systems have some error handling mechanism that will let you
handle these errors.

If you really don't like the exception handling stuff, there is another
option. You can have 'foo' return a unique type of 'condition' object
instead of a result, and then you can check whether that condition was
returned instead of the type of value you expect. Scheme implementations
often have a condition system to go along with their exception system, and
you can use this to create a unique condition for your situation, if you
so choose, or you could use one of the existing situations. Instead of
'raising' the condition like you would normally do, you can just return it
instead of the result that the caller expects, and then handle the error
that way.

Aaron W. Hsu

--
Of all tyrannies, a tyranny sincerely exercised for the good of its
victims may be the most oppressive. -- C. S. Lewis

marcomaggi

unread,
Jul 2, 2009, 4:22:36 PM7/2/09
to
On Jul 2, 6:20 pm, Elena <egarr...@gmail.com> wrote:
> I'm wrapping some C functions using FFI and I'd like to know how would
> you deal with functions which either fail or return more than one
> value. Currently I'm using "values" and "let-values" like this:

I am writing a POSIX interface using the FFI of R6RS Schemes (which
support exceptions). At low level, I declare functions like this:

(define-c-function/with-errno platform-read
(ssize_t read (int void* size_t)))

where "define-c-function/with-errno" is a macro that interfaces with
the FFI of the underlying Scheme implementation. The wrapper Scheme
function is set up to return two values with "values": The first being
the return value of the function (the "ssize_t" type) and the second
being the value of the C language "errno" variable right after the
call.

At the middle level, I do:

(define-syntax temp-failure-retry-minus-one
(syntax-rules ()
((_ ?funcname (?primitive ?arg ...) ?irritants)
(let loop ()
(receive (result errno)
(?primitive ?arg ...)
(when (= -1 result)
(when (= EINTR errno)
(loop))
(raise-errno-error (quote ?funcname) errno ?irritants))
result)))))

(define-syntax do-read-or-write
(syntax-rules ()
((_ ?funcname ?primitive ?fd ?pointer ?number-of-bytes)
(temp-failure-retry-minus-one
?funcname
(?primitive ?fd ?pointer ?number-of-bytes)
?fd))))

(define (primitive-read fd pointer number-of-bytes)
(do-read-or-write primitive-read
platform-read fd pointer number-of-bytes))

it seems a little scary, at first, but all it does is define
"primitive-read" as wrapper for "platform-read". When "platform-read"
returns a result of -1 and an "errno" of EINTR it is called again and
again until it returns something different; when "different" happens
it: If it is a success, it returns the return value, else it throws an
exception describing the "errno" value.

The two values are received with "receive" a macro defined by an SRFI
which has a little less verbose syntax than "let-values" when values
are to be received by a single form.

(define-syntax receive
(syntax-rules ()
[(_ formals expression b b* ...)
(call-with-values
(lambda () expression)
(lambda formals b b* ...))]))

I omit the explanation of the higher level, because it is not relevant
here. Anyway, returning multiple values is all right.

Another example, the C function "readdir()" is implemented in such a
way that checking "errno" is the only way to distinguish between an
error and a particular success condition (no more entries in the
directory). At the lower level I declare it as:

(define-c-function/with-errno platform-readdir
(pointer readdir (pointer)))

and at the middle level:

(define (primitive-readdir stream)
(receive (result errno)
(platform-readdir stream)
;;Here we assume that errno is set to zero by PLATFORM-
READDIR
;;before the call to the foreign function.
(when (and (pointer-null? result)
(not (= 0 errno)))
(raise-errno-error 'primitive-readdir errno stream))
result))

Yoshi. Multiple values are good.

Elena

unread,
Jul 3, 2009, 6:21:48 AM7/3/09
to
On 2 Lug, 17:59, "Aaron W. Hsu" <arcf...@sacrideo.us> wrote:
> I like multiple return values, so let's get that out of the way right now.  
> However, I don't see here why you use multiple return values. An error  
> situation is an exceptional situation, and one where you would usually not
> care to have a "result," since presumably the error occured while trying
> to do something to get that result. Instead, the common idiom here is to
> either signal an error in some manner using 'error' or the like (which can
> also be done in an FFI), or to return some value which indicates that the
> computation doesn't have a result in the normal sense.

I don't think so. An error is an error (for instance: failure to open
a file), an exception is an exception (for instance: dereferencing a
null value). And you can't say, by looking at a function call, whether
the function raises exceptions or not. Therefore I prefer regular
error codes. My only issue is to make them noticeable. I think that
the multiple values approach achieves that, since you see that an
error is returned among other parameters (which are set to #f upon
failure)

Thank you for your other suggestion. I'll investigate the condition
system available with PLT Scheme.

Ray Blaak

unread,
Jul 4, 2009, 1:43:17 PM7/4/09
to
Elena <egar...@gmail.com> writes:
> On 2 Lug, 17:59, "Aaron W. Hsu" <arcf...@sacrideo.us> wrote:
> > Instead, the common idiom here is to
> > either signal an error in some manner using 'error' or the like (which can
> > also be done in an FFI), or to return some value which indicates that the
> > computation doesn't have a result in the normal sense.
>
> I don't think so. An error is an error (for instance: failure to open
> a file), an exception is an exception (for instance: dereferencing a
> null value).

I recommend using exceptions for errors as well. They both indicate
"exceptional" control flow, both indicate unexpected situations.

> And you can't say, by looking at a function call, whether
> the function raises exceptions or not.

You cannot determine the errors by looking at a function call either.

Consider what you actually do in the face of exceptions: stop what you are
doing, report the situation.

Consider what you actually do in the face of "errors": stop what you are
doing, report the situation.

So, use a single mechanism that forces you not to ignore problems, and aborts
control flow that no longer can assume correct behaviour.

> Therefore I prefer regular error codes. My only issue is to make them
> noticeable.

Exceptions are far more noticable since you are forced to deal with them,
preferrably at the top level.

In practice error codes are easily ignored, and code continues merrily on
assuming things are correct, which is the worst outcome.

--
Cheers, The Rhythm is around me,
The Rhythm has control.
Ray Blaak The Rhythm is inside me,
rAYb...@STRIPCAPStelus.net The Rhythm has my soul.

Aaron W. Hsu

unread,
Jul 4, 2009, 3:44:47 PM7/4/09
to
On Sat, 04 Jul 2009 13:43:17 -0400, Ray Blaak
<rAYb...@stripcapstelus.net> wrote:

> Exceptions are far more noticable since you are forced to deal with them,
> preferrably at the top level.

I have to disagree here. For very stable, mature programs that have to
deal with error conditions, you don't want them going up to the top-level
and messing with the entire program. You want these exceptions isolated to
the most localized place possible that allows them to be fixed or allows
the program to go on to something else. For example, a program trying to
read a file may simultaneously be running something else in another thread
somewhere, or may be polling to do something else. You don't want the
whole application to stop when opening that single file fails, but you
want just the parts that rely on it to stop, and maybe display an error to
the user.

The nice thing about exceptions is that they make it clear when you are
handling errors and what you do with them, rather than embedding them
somewhere into the rest of the system, such as with an if expression.

> In practice error codes are easily ignored, and code continues merrily on
> assuming things are correct, which is the worst outcome.

Indeed, this is a very bad thing.

Elena

unread,
Jul 4, 2009, 4:52:43 PM7/4/09
to
On 4 Lug, 19:43, Ray Blaak <rAYbl...@STRIPCAPStelus.net> wrote:
> You cannot determine the errors by looking at a function call either.

In Scheme, if a function returns a value, you can:

(let-values (((error result) (my-function)))
;; "error" must be checked here
...)

In C++ you could always force a caller to check the return code.

IMO, exceptions work in theory, but fail to deliver in practice. Code
which uses them becomes not clear. I look at exceptions as modern
gotos. Maybe that's just a limitation of current tools: they could
show you how many errors a call can raise, and/or quickly jump where
such exception is handled (maybe such a tool already exists).

BTW, I do agree that code with error codes is hairier than code with
exceptions.

Ray Blaak

unread,
Jul 4, 2009, 6:54:30 PM7/4/09
to
"Aaron W. Hsu" <arc...@sacrideo.us> writes:
> I have to disagree here. For very stable, mature programs that have to deal
> with error conditions, you don't want them going up to the top-level and
> messing with the entire program. You want these exceptions isolated to the
> most localized place possible that allows them to be fixed or allows the
> program to go on to something else. For example, a program trying to read a
> file may simultaneously be running something else in another thread
> somewhere, or may be polling to do something else. You don't want the whole
> application to stop when opening that single file fails, but you want just
> the parts that rely on it to stop, and maybe display an error to the user.

I am not so sure we actually disagree.

Of course you don't want to stop the program.

My experience with exceptions (admittedly with Java as opposed to Scheme) is
that people "handle" them by printing the stack trace.

There is a danger with swallowing things too early in that parent code can
continue thinking everything is fine.

In my experience, entire user actions are only what needs to be aborted, but
the application's UI is still active to respond to the next action.

Also in my experience it is very rare that errors can be recovered from, and
even if they are it is usually better for a human to analyze the problem
anyway. Usually a situation is unexpected, e.g. disk full, network down, etc.

In practice one can only safely indicate there is a problem to the user, and
not do anything further for the current action (but the app still stays alive
for other actions).

Of course, for controlled situations where errors are actually expected you
can take corrective actions. Typical examples are tentative parsing, and
trying alternatives when they fail.

Ray Blaak

unread,
Jul 4, 2009, 7:04:03 PM7/4/09
to
Elena <egar...@gmail.com> writes:
> On 4 Lug, 19:43, Ray Blaak <rAYbl...@STRIPCAPStelus.net> wrote:
> > You cannot determine the errors by looking at a function call either.
>
> In Scheme, if a function returns a value, you can:
>
> (let-values (((error result) (my-function)))
> ;; "error" must be checked here
> ...)
>
> In C++ you could always force a caller to check the return code.

How do you do this? Function results can be ignored.

When I review C++ code I always require the use of exceptions to indicate
errors instead of error return codes.

>
> IMO, exceptions work in theory, but fail to deliver in practice. Code
> which uses them becomes not clear. I look at exceptions as modern
> gotos.

My opinion is the opposite: with exceptions the main code becomes uncluttered,
showing the "normal" logic. 90+% of the code is not worried about exceptions
at all, since only special top level handlers need to exist.

Elena

unread,
Jul 5, 2009, 5:29:46 AM7/5/09
to
On 5 Lug, 01:04, Ray Blaak <rAYbl...@STRIPCAPStelus.net> wrote:

> Elena <egarr...@gmail.com> writes:
> > In C++ you could always force a caller to check the return code.
>
> How do you do this? Function results can be ignored.

http://www.ddj.com/cpp/184401233 (jump to page 3 for the article)

> > IMO, exceptions work in theory, but fail to deliver in practice. Code
> > which uses them becomes not clear. I look at exceptions as modern
> > gotos.
>
> My opinion is the opposite: with exceptions the main code becomes uncluttered,
> showing the "normal" logic. 90+% of the code is not worried about exceptions
> at all, since only special top level handlers need to exist.

I agree wholeheartly when it comes to visual clarity, but not when we
talk about control flow clarity. I've found control flow difficult to
understand when looking at code which used exceptions to handle
errors. You don't handle every error at top level.

Java kind of mitigates that by requiring exception clauses, but they
are a burden when prototyping.

Elena

unread,
Jul 5, 2009, 5:42:03 AM7/5/09
to
On 5 Lug, 11:29, Elena <egarr...@gmail.com> wrote:
> http://www.ddj.com/cpp/184401233(jump to page 3 for the article)

To summarize it for those who don't speak C++, you return an error
object whose destructor fires an assertion if it has not been checked
beforehand. That needs to be done only in debug releases, you can then
use regular error codes in production releases.

Ray Blaak

unread,
Jul 5, 2009, 9:06:15 PM7/5/09
to
Elena <egar...@gmail.com> writes:

> On 5 Lug, 01:04, Ray Blaak <rAYbl...@STRIPCAPStelus.net> wrote:
> > Elena <egarr...@gmail.com> writes:
> > > In C++ you could always force a caller to check the return code.
> >
> > How do you do this? Function results can be ignored.
>
> http://www.ddj.com/cpp/184401233 (jump to page 3 for the article)

Ugh. Sorry, this makes work tedious. The whole point of exceptions is not to
handle them for the most part. That's what makes things easy.

> I agree wholeheartly when it comes to visual clarity, but not when we
> talk about control flow clarity. I've found control flow difficult to
> understand when looking at code which used exceptions to handle
> errors. You don't handle every error at top level.

You handle the vast marjority at the top level. The few that you don't tend to
have exception handlers quite close by.

I completely disagree that exception handling obscures control flow, that it
is a kind of goto. I can see that problem in theory, but in practice those
problems don't arise.

Or, rather they should not: using exceptions to affect alternatives in
"normal" control should be avoided, used only in unusually and explicitly
justified circumstances.

Exceptions should be used for errors, for "exceptional" circumstances, to
basically abort the control flow. In such scenarios, the destination is
conceptually clear: stop what was happening and reset.

> Java kind of mitigates that by requiring exception clauses, but they
> are a burden when prototyping.

Indeed. In fact, I now try to actually remove checked exception clauses in
favour of unchecked exceptions, or just use a "throws Exception" clause, so
that the tedium is avoided, and that artificial API changes are minimized.

In practice I found that checked exceptions were not fundamentally helping and
they usually caused more work or bugs: people would handle them prematurely
to shut the compiler up, which made things worse by masking errors. Or, they
rewrapped them into the next level's checked exception type and rethrew.

Either way it's tedious.

Once I realized (in conjunction with reading the debates on the net) that all
I really needed to do was handle exceptions in a few key places, I was able to
avoid the burden of checked exceptions and the fragility of error return codes.

Elena

unread,
Jul 8, 2009, 4:26:01 AM7/8/09
to
On 6 Lug, 01:06, Ray Blaak <rAYbl...@STRIPCAPStelus.net> wrote:
> Elena <egarr...@gmail.com> writes:
> > On 5 Lug, 01:04, Ray Blaak <rAYbl...@STRIPCAPStelus.net> wrote:
> > > Elena <egarr...@gmail.com> writes:
> > > > In C++ you could always force a caller to check the return code.
>
> > > How do you do this? Function results can be ignored.
>
> >http://www.ddj.com/cpp/184401233(jump to page 3 for the article)

>
> Ugh. Sorry, this makes work tedious.

Indeed.

> The whole point of exceptions is not to
> handle them for the most part. That's what makes things easy.
>
> > I agree wholeheartly when it comes to visual clarity, but not when we
> > talk about control flow clarity. I've found control flow difficult to
> > understand when looking at code which used exceptions to handle
> > errors. You don't handle every error at top level.
>
> You handle the vast marjority at the top level. The few that you don't tend to
> have exception handlers quite close by.
>
> I completely disagree that exception handling obscures control flow, that it
> is a kind of goto. I can see that problem in theory, but in practice those
> problems don't arise.
>
> Or, rather they should not: using exceptions to affect alternatives in
> "normal" control should be avoided, used only in unusually and explicitly
> justified circumstances.
>
> Exceptions should be used for errors, for "exceptional" circumstances, to
> basically abort the control flow. In such scenarios, the destination is
> conceptually clear: stop what was happening and reset.

Yes, it should be that way. In practice, they are used for common
errors (like errors on opening files, etc.), therefore you have to
handle them for the most part, possibly in outer functions, thus
achieving a goto-like functionality.

Would you abort control flow just because opening a file fails, or a
connection has been refused, or because there has been an receiveing
data. and so on?

Elena

unread,
Jul 8, 2009, 5:41:40 AM7/8/09
to
Anyway I think we better hang up on this. I think xceptions vs error
codes has been matter for debate since exception have been introduced.
Kind of Scheme vs Common Lisp ^_^

See also:

http://www.joelonsoftware.com/items/2003/10/13.html

It pretty much summarize my point of view. And I think that all
retorts to this article pretty much summarize yours.

Ray Blaak

unread,
Jul 10, 2009, 3:19:13 AM7/10/09
to
Elena <egar...@gmail.com> writes:
> Would you abort control flow just because opening a file fails, or a
> connection has been refused, or because there has been an receiveing
> data. and so on?

I read you on hanging up on this, so let me leave with saying here that yes,
I believe you should abort control flow for these issues.

What you do is tell the user of the failure, and the user can direct the app
to try something else (e.g. save your document to somewhere else).

0 new messages