Pass Multiple returns to a function.

2,978 views
Skip to first unread message

Tom Carstens

unread,
Mar 19, 2012, 10:43:54 PM3/19/12
to golang-nuts
So what I'm trying to do is something like this:

func function1(a,b int) {...}
func function2(a int) (int, int) {...}
...
function1(function2(1))

So is it possible to directly pass a function that returns multiple
values into another one? Or do I have to set the returns to something
and then pass them in?

Tarmigan

unread,
Mar 20, 2012, 1:03:27 AM3/20/12
to Tom Carstens, golang-nuts
Did you try it?
http://play.golang.org/p/T6WIcT_8q5

-Tarmigan

Tom Carstens

unread,
Mar 20, 2012, 2:38:10 AM3/20/12
to golang-nuts
Okay... so it works that way. However, it doesn't work if I change it
slightly. (as a note, i'm trying to write a centralized error handling
function so I'm actually passing in a bit more data.)

so how about this then:
http://play.golang.org/p/dh0h1pYftn

On Mar 20, 12:03 am, Tarmigan <tarmigan+gol...@gmail.com> wrote:
> Did you try it?http://play.golang.org/p/T6WIcT_8q5

Tom Carstens

unread,
Mar 20, 2012, 2:54:42 AM3/20/12
to golang-nuts
http://code.google.com/p/go/issues/detail?id=973

so apparently I'm simply not allowed to add the additional arguments
I'd like to add...

Tom Carstens

unread,
Mar 20, 2012, 3:20:40 AM3/20/12
to golang-nuts
ah well, here's my temporary solution if you're curious.

//check to see if an error has occured and deal with it
package assert
import "os"
//a prefix to the error message
var Prefix string
//the error texts that are allowable
var AcceptErrors []string
//a function to run when an unallowable error has occured
//usually os.Exit
var ErrorFoundFunc = os.Exit
//a value to pass to ErrorFoundFunc
//for os.Exit it's usually going to be 1
var ErrorFoundVal = 1
//should the error message be silenced
var Silent bool
type value interface{}
//function that checks to see if an error has occured and handles it
//
//example usage:
//file = assert.Check(os.Open("file")).(*os.File)
//
//you'll have to cast the returned value, as in order to make Check
general
//purpose it uses an empty interface for the non-error value
func Check(v value, err error) value {
if err != nil {
for _, acceptable := range AcceptErrors {
if err.Error() == acceptable {
return v
}
}

if !Silent {
os.Stdout.WriteString(Prefix + err.Error())
}
ErrorFoundFunc(ErrorFoundVal)
}
return v

Jesse McNelis

unread,
Mar 20, 2012, 5:22:36 AM3/20/12
to Tom Carstens, golang-nuts
On Tue, Mar 20, 2012 at 1:43 PM, Tom Carstens <tomca...@gmail.com> wrote:
> So is it possible to directly pass a function that returns multiple
> values into another one? Or do I have to set the returns to something
> and then pass them in?

This is something easily testable.
Put your code at http://play.golang.org and see if it works how you'd
want it to.

--
=====================
http://jessta.id.au

David Anderson

unread,
Mar 20, 2012, 5:30:48 AM3/20/12
to Tom Carstens, golang-nuts
http://golang.org/doc/go_spec.html#Calls

"As a special case, if the return parameters of a function or method g are equal in number and individually assignable to the parameters of another function or method f, then the call f(g(parameters_of_g)) will invoke f after binding the return values of g to the parameters of f in order. The call of f must contain no parameters other than the call of g. If f has a final ... parameter, it is assigned the return values of g that remain after assignment of regular parameters."

That specifies the exact behavior you're requesting, and also excludes what you're trying further down (additional params).

- Dave

Tom Carstens

unread,
Mar 20, 2012, 9:13:47 AM3/20/12
to golan...@googlegroups.com, Tom Carstens
yes, I found that after I posted (I originally looked under the ... section.) however it seems to me that what I wanted to do was an intuitively obvious thing to do (given multiple return values)

John Asmuth

unread,
Mar 20, 2012, 9:39:32 AM3/20/12
to golan...@googlegroups.com, Tom Carstens
You'll find that Go contains a number of safety rails, like this, that discourage developers from doing things that will make their code harder to deal with in the future by forcing them to write one more line today.

Jan Mercl

unread,
Mar 20, 2012, 9:43:04 AM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 2:13:47 PM UTC+1, Tom Carstens wrote:
yes, I found that after I posted (I originally looked under the ... section.) however it seems to me that what I wanted to do was an intuitively obvious thing to do (given multiple return values)

The "exact number of results as params" rule among other possible issues avoids things like:

func foo() int {}

func bar() (int, int) {}

func baz(a, b, c int) {}

func main() { baz(foo(), bar(); baz(bar, foo) }

In the case of a variadic function, one can pass a slice to it with mySlice..., but multiple valued function result is not a slice.

Jan Mercl

unread,
Mar 20, 2012, 9:44:40 AM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 2:43:04 PM UTC+1, Jan Mercl wrote:


Err, I've missed a couple of parenthesis, sorry.

func main() { baz(foo(), bar()); baz(bar(), foo()) } 

Tom Carstens

unread,
Mar 20, 2012, 9:56:45 AM3/20/12
to golan...@googlegroups.com
I really don't see how that code is confusing.
You have a function with 3 parameters and two functions with 1 and 2 returns. It should be fairly straight forward to figure out how many returns a function has and how many parameters are available. Using currying:

baz(foo(),bar())
becomes
baz(foo())(bar.ret1)(bar.ret2))

and

baz(bar(), foo())
becomes
baz(bar.ret1)(bar.ret2)(foo())

There really isn't anything all that interesting about this code. If you don't want to use currying then making multi-returns a tuple (with similar ... syntax as a slice) would also solve the problem. Further, the originally allowed case's implementation falls out of the code it would take to implement this.

Tom Carstens

unread,
Mar 20, 2012, 10:03:38 AM3/20/12
to golan...@googlegroups.com, Tom Carstens
If that were really the motivation for this then I'd accept it. But it seems to me the general case of expanding multiple returns into a parameter space (that has a greater number of parameters then returns) neatly solves the problem of expansion into the exact number of parameters as well. Further, there's multiple possible solutions for how to go about solving the problem (I'd prefer slice like ... behavior, since that seems the most natural operator for the job.)

andrey mirtchovski

unread,
Mar 20, 2012, 10:10:19 AM3/20/12
to Tom Carstens, golan...@googlegroups.com
the rule is there because otherwise you'd have to add a new rule to
make sure that none of the callee-s accept variadic arguments. it's
much simpler to add one line of code per function call than to add a
complicated rule to the spec. Jan's example above is already difficult
to parse and error prone.

Jan Mercl

unread,
Mar 20, 2012, 10:11:43 AM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 2:56:45 PM UTC+1, Tom Carstens wrote:
I really don't see how that code is confusing.
You have a function with 3 parameters and two functions with 1 and 2 returns. It should be fairly straight forward to figure out how many returns a function has and how many parameters are available. Using currying:

baz(foo(),bar())
becomes
baz(foo())(bar.ret1)(bar.ret2))

and

baz(bar(), foo())
becomes
baz(bar.ret1)(bar.ret2)(foo())
 
There really isn't anything all that interesting about this code. If you don't want to use currying then making multi-returns a tuple (with similar ... syntax as a slice) would also solve the problem. Further, the originally allowed case's implementation falls out of the code it would take to implement this.

The problem is not at all in that the compiler cannot count to 3 and compare the needed vs provided number of things. The problem is that some people (me included) think this is a neat way how to code unreadable programs (and BTW a new source of hard to see bugs - in the example switching of foo() and bar() ordering would make no difference to the compiler).

That opinion or something in its line seems to be also the stance of the dev team.

Tom Carstens

unread,
Mar 20, 2012, 10:17:39 AM3/20/12
to golan...@googlegroups.com
Whereas, to me that code is perfectly legible and obvious what is happening. I understand that there's very little chance of a change occurring (the bug report I linked to was 1.5yrs ago or so.) However, I hope you'll agree that talking about why decisions were made and revisiting them every now and then to make sure the choice was correct is a good thing.

chris dollin

unread,
Mar 20, 2012, 10:20:41 AM3/20/12
to Jan Mercl, golan...@googlegroups.com
On 20 March 2012 14:11, Jan Mercl <0xj...@gmail.com> wrote:

> The problem is not at all in that the compiler cannot count to 3 and compare
> the needed vs provided number of things. The problem is that some people (me
> included) think this is a neat way how to code unreadable programs (and BTW
> a new source of hard to see bugs - in the example switching of foo() and
> bar() ordering would make no difference to the compiler).
>
> That opinion or something in its line seems to be also the stance of the dev
> team.

I've used (quite a bit) a programming language which allowed
"mixing" of argument values like this, and people didn't seem to find
it a problem.

Well, sometimes it was, because Pop11 is dynamically typed and
with an open stack, so you can get mad errors if you pull or push
one too many arguments/results. But Go is neither -- that kind of
error is caught at compile time by routine type checking, and the
more subtle kinds of error (when you exchange two parameters of
the same type) can (a) happen anyway without mixing and (b) is
catchable by the tests suites you'd already expect to have.

So I think allowing mixing of argument values is pretty much
a non-problem, and it's certainly convenient and powerful,
but the Go team has its own preferences and I don't expect
Go to grow mixing -- this isn't a request for it, just a raising
of "Mixing OK!" hands.

Chris

--
Chris "allusive" Dollin

John Asmuth

unread,
Mar 20, 2012, 10:49:35 AM3/20/12
to golan...@googlegroups.com
When you write it yourself, perhaps.

But let's say that you're using someone else's library, and you do

Foo(f1(), f2(), f3(), f4(), f5(), f6(), f7(), f8(), f9(), f10())

And the developer of f9() changes the number of values returned. Foo() now reports that you have too many or too few arguments, and you have to check 10 different places and remember how each of them are supposed to match Foo()'s parameters.

Whereas now, since you have to extract each of the functions on its own line, there is only one place to check, and how the returns match the parameters is clear.

andrey mirtchovski

unread,
Mar 20, 2012, 10:52:15 AM3/20/12
to John Asmuth, golan...@googlegroups.com
> Foo(f1(), f2(), f3(), f4(), f5(), f6(), f7(), f8(), f9(), f10())
>
> And the developer of f9() changes the number of values returned. Foo() now
> reports that you have too many or too few arguments, and you have to check
> 10 different places and remember how each of them are supposed to match
> Foo()'s parameters.


Or worse: the compiler doesn't report an error (Foo() takes a variadic
argument) and your code silently breaks.

chris dollin

unread,
Mar 20, 2012, 10:58:46 AM3/20/12
to andrey mirtchovski, John Asmuth, golan...@googlegroups.com

Fails its tests?

> silently breaks.

Maybe.

--
Chris "allusive" Dollin

chris dollin

unread,
Mar 20, 2012, 11:09:24 AM3/20/12
to John Asmuth, golan...@googlegroups.com
On 20 March 2012 14:49, John Asmuth <jas...@gmail.com> wrote:
> When you write it yourself, perhaps.
>
> But let's say that you're using someone else's library, and you do
>
> Foo(f1(), f2(), f3(), f4(), f5(), f6(), f7(), f8(), f9(), f10())
>
> And the developer of f9() changes the number of values returned. Foo() now
> reports that you have too many or too few arguments,

... and where the mismatch occurs ...

> and you have to check 10 different places

Or the place where the error is spotted ...

> and remember how each of them are supposed to match
> Foo()'s parameters.

It's often possible to construct horrible examples (examples
showing horrors rather than being horrible) of any language feature;
assignments, expressions, nested if statements, polymorphism,
nil, use of heap store, &co, but the mere fact that horrid constructions
are possible doesn't mean that the constructions should be omitted.

I think that if you have a ten-argument function you're probably
already in trouble; multiplicity errors are just the canary.

John Asmuth

unread,
Mar 20, 2012, 11:11:56 AM3/20/12
to golan...@googlegroups.com, John Asmuth
On Tuesday, March 20, 2012 11:09:24 AM UTC-4, ehedgehog wrote:
On 20 March 2012 14:49, John Asmuth <jas...@gmail.com> wrote:
> And the developer of f9() changes the number of values returned. Foo() now
> reports that you have too many or too few arguments,

... and where the mismatch occurs ...

How could the compiler possibly know? What if the types match?
 

> and you have to check 10 different places

Or the place where the error is spotted ...

Once again, how could the compiler know? How can the coder know without looking at each of the functions?
 

Tom Carstens

unread,
Mar 20, 2012, 11:13:58 AM3/20/12
to golan...@googlegroups.com
For starters, Foo is way too complicated. Taking at least 11 parameters is insane (unless it's a variadic argument situation, in which case it would be reasonable.)

Secondly, a library that can throw in random changes in return values between releases (and not release a notice about breaking backwards compatibility) is not a good library to use. If the return of bar() changes from an int to a string between releases for no apparent reason, you know there's something wrong going on (and additional returns are a problem of the same nature.)

Thirdly, this isn't an either/or situation. You can still use separate parameters if you find it easier to read. But for short things (in particular the code I was working on,) having the ability to mix multi-return functions as parameters makes life easy. 

chris dollin

unread,
Mar 20, 2012, 11:24:09 AM3/20/12
to John Asmuth, golan...@googlegroups.com
On 20 March 2012 15:11, John Asmuth <jas...@gmail.com> wrote:
> On Tuesday, March 20, 2012 11:09:24 AM UTC-4, ehedgehog wrote:
>>
>> On 20 March 2012 14:49, John Asmuth <jas...@gmail.com> wrote:
>> > And the developer of f9() changes the number of values returned. Foo()
>> > now
>> > reports that you have too many or too few arguments,
>>
>> ... and where the mismatch occurs ...
>
> How could the compiler possibly know?

From the type mismatches that will probably have occurred.

> What if the types match?

If the types match, then yes, you have a harder problem. You
already had a problem, because with that much type matching,
swopping fi() and fj() is likely going to silently fail. I think
that error is rather more likely than someone arbitrarily and
silently changing the multiplicity of a function result.

Jan Mercl

unread,
Mar 20, 2012, 11:40:38 AM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 3:17:39 PM UTC+1, Tom Carstens wrote:
However, I hope you'll agree that talking about why decisions were made and revisiting them every now and then to make sure the choice was correct is a good thing.

Sure, no problem.

Jesse McNelis

unread,
Mar 20, 2012, 11:49:53 AM3/20/12
to Tom Carstens, golan...@googlegroups.com
On Wed, Mar 21, 2012 at 2:13 AM, Tom Carstens <tomca...@gmail.com> wrote:
> Secondly, a library that can throw in random changes in return values
> between releases (and not release a notice about breaking backwards
> compatibility) is not a good library to use. If the return of bar() changes
> from an int to a string between releases for no apparent reason, you know
> there's something wrong going on (and additional returns are a problem of
> the same nature.)

The point is that this whole problem can be avoided with a few lines of code.
Lines of code are cheap, lines of code that just assign to variables
are even cheaper.
These lines are so cheap that you don't have to think to write them or
read them.

You spent more time in this thread than you'll ever spend
writing/reading those particular lines of code.

--
=====================
http://jessta.id.au

Tom Carstens

unread,
Mar 20, 2012, 11:57:17 AM3/20/12
to golan...@googlegroups.com, Tom Carstens
I disagree. Rewriting the same lines over and over is a waste of time. The assert.Check() function I provided above saved me 76 lines of code on a project I'm only 10% done with. Further more, with my addition I'd save about double that. Saving nearly 1.5 kloc isn't something to scoff at. And this is only talking about error handling, I'm sure I could engineer plenty of other places to use such a feature to save redundant typing.

Jan Mercl

unread,
Mar 20, 2012, 12:11:25 PM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 4:57:17 PM UTC+1, Tom Carstens wrote:
I disagree. Rewriting the same lines over and over is a waste of time. The assert.Check() function I provided above saved me 76 lines of code on a project I'm only 10% done with. Further more, with my addition I'd save about double that. Saving nearly 1.5 kloc isn't something to scoff at. And this is only talking about error handling, I'm sure I could engineer plenty of other places to use such a feature to save redundant typing.

The savings are probably temporary only. Once you will have to return to your code a year later (or anyone else just now), it IMO wastes reading time (more precious, code is more often read than written). Anyone will have [again] figure out why is the nice and short type literal 'interface{}' [re]named to 'value' for no good reason (sans the worsened reading). The same or similar applies to "centralized" error handling and/or assert.Check (CheckEquals, ...). It makes simple things/logic visible in a single line/few lines of code go somewhere else/away. The reading/comprehending context is lost. IMO the total "gain" is a negative value not in the near future but right now.

Even that being said, it's no critique of your coding style. It's just an explanation why I don't do such things (and I used to do them before I got burnt ;-)

Tom Carstens

unread,
Mar 20, 2012, 12:22:21 PM3/20/12
to golan...@googlegroups.com
wait I can use interface{}? um... one sec let me change that (I don't do too much with interfaces so I really didn't know that worked.)

I understand your point but when you have :

file, err := os.Open("some file")
if err != nil {
    fmt.Println("this prog: ", err)
    os.Exit(1)
}
defer file.Close()

Written a dozen times in a few different files it just starts becoming a pain. Whereas in my project I can do this:
file = assert.Check(os.Open("some file")).(*os.File)
defer file.Close()

I really wish the type assertion wasn't necessary but unless generics are added it'll be impossible to remove. Further, I like that assert.Check() because it also works for strconv functions or any other error prone act.

John Asmuth

unread,
Mar 20, 2012, 12:27:02 PM3/20/12
to golan...@googlegroups.com


On Tuesday, March 20, 2012 11:13:58 AM UTC-4, Tom Carstens wrote:
Thirdly, this isn't an either/or situation. You can still use separate parameters if you find it easier to read. But for short things (in particular the code I was working on,) having the ability to mix multi-return functions as parameters makes life easy. 

What developers *can* do they *will* do. Even if you manage to restrict yourself to the "sensible" subset of the language, not everyone else will, and eventually you'll have to deal with some other person's code that does something ridiculous.

Go tries to have the "sensible" subset be as close to the whole language as possible.

Tom Carstens

unread,
Mar 20, 2012, 12:34:52 PM3/20/12
to golan...@googlegroups.com
I'm still not convinced that it isn't sensible. However, the big improvement is not in sensibility, but in symmetry. As it allows all functions as parameters to be parsed in only one way (currently, multi and single returned functions handle differently.)

Jan Mercl

unread,
Mar 20, 2012, 12:35:05 PM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 5:22:21 PM UTC+1, Tom Carstens wrote:
I understand your point but when you have :

file, err := os.Open("some file")
if err != nil {
    fmt.Println("this prog: ", err)
    os.Exit(1)
}
defer file.Close()

Nice, I know exactly what is happening here w/o a need to learn about the assert.Check() as bellow.
 
Written a dozen times in a few different files it just starts becoming a pain. Whereas in my project I can do this:
file = assert.Check(os.Open("some file")).(*os.File)
defer file.Close()

What is this doing? Why the type assertion??? Wait, let's see what is that assert.Check about...


I really wish the type assertion wasn't necessary but unless generics are added it'll be impossible to remove.

file, err := os.Open(fname) // Look ma, no type assertion ;-)

andrey mirtchovski

unread,
Mar 20, 2012, 12:39:41 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
> file, err := os.Open("some file")
> if err != nil {
>     fmt.Println("this prog: ", err)
>     os.Exit(1)
> }
> defer file.Close()

rewrite this as:

file, err := os.Open("some file")
if err != nil {

log.Fatal(err)
}
defer file.Close()

and now you're talking about 15-20 character difference with your
assert.Check call. with this you haven't introduced silent allocation
(including potential string concatenation with '+'), you're not
looping through a list of potentially "acceptable" errors, you are not
using string comparisons to handle errors, you're not dereferencing
interface values, and you won't exit just because someone mistyped
'open' as 'opne' turning an acceptable error string into an
unacceptable one (which, if and when it happens, you'll have to carry
in your 'acceptable' slice for as long as you have users)...

i'm not saying assert.Check is unacceptable. i'm saying it would be a
lot more complex to get right.

while on the topic of error handling, this humorous take on java
exceptions made me smile today. use it as a counter-example of the
usefulness of a generic 'assert':
http://java.dzone.com/articles/filtering-stack-trace-hell

Tom Carstens

unread,
Mar 20, 2012, 12:40:12 PM3/20/12
to golan...@googlegroups.com
On that same note, moving error handling elsewhere has significantly decluttered my code. While I may feel differently a year from now (we'll see) assert.Check's semantics are relatively simple and shouldn't require much effort to learn (as it's essentially one line error checking, though doing more complicated things then checking an error and exiting do require a bit of finesse.)

Tom Carstens

unread,
Mar 20, 2012, 12:49:47 PM3/20/12
to golan...@googlegroups.com
lol, honestly I think the type assertion is the only magic in that line. But as you pointed out, I recently made it so I wouldn't know. If the name is confusing, what would be a better one?

Another example:
_, err1 := file1.Read(buf1)
_, err2 := file2.Read(buf2)
if err1 != nil || err2 != nil {
	if err1 == nil {
		if !*silent {
			fmt.Printf("%s on %s\n", err2, file2.Name())
		}
	} else if err2 == nil {
		if !*silent {
			fmt.Printf("%s on %s\n", err1, file1.Name())
		}
	}
	os.Exit(1)
}
becomes:
assert.Suffix = fmt.Sprintf(" on %s\n", file1.Name())
assert.Check(file1.Read(buf1))
assert.Suffix = fmt.Sprintf(" on %s at char %d\n", file2.Name(), char)
assert.Check(file2.Read(buf2))

Maybe I'm just weird but all that whitespace in the first one makes it less clear what's going on. Again, I can read the second easier than other people because I wrote it, but I really do feel that the second is cleaner and less error prone.

Paul Borman

unread,
Mar 20, 2012, 12:53:30 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
I must fully concur with Jan on this code snippet:
 
file = assert.Check(os.Open("some file")).(*os.File)
defer file.Close()

I can make assumptions about what Check might be doing.  The type assertion means a coding mistake might not be noticed until runtime.  I have no idea what happens if the Open does fail (I must assume a panic since I am no longer properly checking a return value).

A shortcut by me today often leads to headaches tomorrow (or outright confusion for others).  To alleviate that, this assert.Check would need to become the norm, and that would still not alleviate the issue that Check will be potentially doing something unique in each case, and possibly not what I want it to do.  The code no longer does what it say son the page, at least, not obviously.

John Asmuth

unread,
Mar 20, 2012, 12:55:13 PM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 12:34:52 PM UTC-4, Tom Carstens wrote:
However, the big improvement is not in sensibility, but in symmetry.

This is an argument that, historically, the Go designers do not take seriously.

It's an example of the NJ vs MIT approach: http://www.jwz.org/doc/worse-is-better.html

The Go designers are solidly in the NJ camp.

Tom Carstens

unread,
Mar 20, 2012, 12:55:17 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
Certainly, assert.Check is bordering on too generic (I actually removed the ErrorFoundFunc and Val because I ended up not needing them.) And I never did like java's exceptions so I don't mean to do copy it (which is why if an error is found it simply quits, no throw craziness.)

However, go produces a lot of errors (actually coding is just error prone in general) and it gets tedious to write the same thing over and over again when I can write it once and just refer to that.

Tom Carstens

unread,
Mar 20, 2012, 12:56:45 PM3/20/12
to golan...@googlegroups.com
but it doesn't matter because you haven't shown that it isn't sensible too.

Tom Carstens

unread,
Mar 20, 2012, 1:03:38 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
Then how do I improve it? There are countless loc out there for dealing with errors. If that can be compressed to a single line per error isn't that an improvement?

On a different note, in a presentation at Google I/O 2011 (Writing Web Apps, I believe but I'm probably wrong) a version of check was used (mine isn't based on it, but they did use it to simplify error handling.) I would think that's sort of an admittance to go being a little verbose on the error handling side of things.

andrey mirtchovski

unread,
Mar 20, 2012, 1:11:46 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
Tom,

The "Error Handling and Go" document has a section on simplifying
error handling, although it doesn't aim at reducing LOC specifically:
http://weekly.golang.org/doc/articles/error_handling.html

Further to this, the Writing Web Applications codewalk has a very good
example (IMO) of writing validators using closures:
http://weekly.golang.org/doc/articles/wiki/

Just tow examples of what's out there. There are also multiple
examples of assert() in go code, but they're highly specialized:
http://golang.org/test/const.go?h=assert#L32

Jan Mercl

unread,
Mar 20, 2012, 1:13:11 PM3/20/12
to golan...@googlegroups.com
On Tuesday, March 20, 2012 5:55:13 PM UTC+1, John Asmuth wrote:
It's an example of the NJ vs MIT approach: http://www.jwz.org/doc/worse-is-better.html

Made me laugh aloud, thank you. From the link: I consider "Unix and C are the ultimate computer viruses." as a very beautiful compliment to the respective authors ;-)

Tom Carstens

unread,
Mar 20, 2012, 1:34:30 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
Honestly, Error Handling and Go's last section, is counter productive. The final code is harder to read than the first bit of code. Generally, throwing more code at a problem doesn't work. If you want to improve something (with the exception of performance, which is tricky) you want to remove code.

The behavior of assert is similar to what I want, but I want to check that an error is nil and that it isn't an acceptable error. So if I were to follow assert's general format:

func check(s string, cond bool) {
    if cond {
        fmt.Println(s)
        os.Exit(1)
    }
}
...
check("my app: " + err.Error(), err != nil && err != acceptable)
...

Does that look better?

John Asmuth

unread,
Mar 20, 2012, 1:34:48 PM3/20/12
to golan...@googlegroups.com
I showed an example with ten functions being packed into a parameter list, where fixing a compile error would be difficult. It didn't seem sensible.

Tom Carstens

unread,
Mar 20, 2012, 1:39:40 PM3/20/12
to golan...@googlegroups.com
and it wasn't a sensible example. I could show that even with go's implementation you can do horrible things with pointers. That doesn't mean pointers shouldn't exist.

John Asmuth

unread,
Mar 20, 2012, 1:41:22 PM3/20/12
to golan...@googlegroups.com
That's because the cost of not having pointers is much greater than the cost of having it possible to do whatever horrible things you're thinking of.

The cost of adding an extra line per function call isn't so bad.

Tom Carstens

unread,
Mar 20, 2012, 1:44:32 PM3/20/12
to golan...@googlegroups.com
Secondly, my argument is an NJ approach. It invokes simplicity in implementation. How can you argue that 1 implementation is more complex then 2 (especially when the code for the 1 is mostly written in one of the 2)?


On Tuesday, March 20, 2012 12:34:48 PM UTC-5, John Asmuth wrote:

Kyle Lemons

unread,
Mar 20, 2012, 1:47:26 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
On Tue, Mar 20, 2012 at 10:34 AM, Tom Carstens <tomca...@gmail.com> wrote:
Honestly, Error Handling and Go's last section, is counter productive. The final code is harder to read than the first bit of code. Generally, throwing more code at a problem doesn't work. If you want to improve something (with the exception of performance, which is tricky) you want to remove code.

The behavior of assert is similar to what I want, but I want to check that an error is nil and that it isn't an acceptable error. So if I were to follow assert's general format:

func check(s string, cond bool) {
    if cond {
        fmt.Println(s)
        os.Exit(1)
    }
}
...
check("my app: " + err.Error(), err != nil && err != acceptable)
...

Does that look better?

Certainly not.  Also, it's adding another line of code to the caller code, so I don't see the savings.  I would send back any code review that included calls to that function.  You're hiding barely one line of code, making it harder to produce a good error message cleanly (try sticking a numeric into your error message), obscuring the consequence of the error (logging? error? panic? -- how do I know if your defers get called?), and making it much harder to tune the error response to a single error without going back to the normal unrolled version.

err := action(args)
check(fmt.Sprintf("myapp: unacceptable error in action(%v): %s", args, err), err != nil && err != acceptable)

vs.

if err := action(args); err != nil && !acceptable(err) {
  log.Fatalf("myapp: unacceptable error in action(%v): %s", args, err)
}

I think the second looks better, but I doubt we're going to convince you at this point.

Tom Carstens

unread,
Mar 20, 2012, 1:48:57 PM3/20/12
to golan...@googlegroups.com
But there's hardly any coast in the first place. The example you gave was a very extreme one that wasn't representative. Nor was the example particularly difficult to read (though that seems to vary depending on the person.) And in order to show that it was "bad" you had to invoke a third-party library that has bad management practices (arbitrarily changing function return types.)

John Asmuth

unread,
Mar 20, 2012, 2:01:27 PM3/20/12
to golan...@googlegroups.com


On Tuesday, March 20, 2012 1:48:57 PM UTC-4, Tom Carstens wrote:
But there's hardly any coast in the first place. The example you gave was a very extreme one that wasn't representative. Nor was the example particularly difficult to read (though that seems to vary depending on the person.) And in order to show that it was "bad" you had to invoke a third-party library that has bad management practices (arbitrarily changing function return types.)

aka the kinds of things that happen in real programming situations.

John Asmuth

unread,
Mar 20, 2012, 2:02:52 PM3/20/12
to golan...@googlegroups.com
But it allows your code to get very confusing indeed, for very little benefit.

Anyway, it seems you've made up your mind. It's clearly a trade-off, and you've clearly valued one side of the trade-off much higher than the other. Others (me, the go designers) value the other side more.

Tom Carstens

unread,
Mar 20, 2012, 2:04:14 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
I'm not trying to be stubborn here, I truly am trying to write code that other people have an easier time with. However, I also don't want to write more than I have to. 

Please note that I was patterning check after assert to see if it was more legible, there's one more feature that I require that's in my original assert.Check(). Namely, I need a mute button. log doesn't have one. Which means I need another if statement, I can choose to do it around my call to log (which means there's an addition 4 lines per log, possibly as low as 2 in corner cases) or I can embed it in check.

That said, you have a really verbose error message style, that I hope is not standard (and I don't think I'll ever create something that would please you.)

Paul Borman

unread,
Mar 20, 2012, 2:04:29 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
I normally dislike anything that calls os.Exit() as *none* of the defers in *any* goroutine are called.  This might be fine for a lock, but maybe not for a defer that flushes buffers.  This is one reason I like to handle errors rather than just let some magical code "do the right thing" that may very well be the wrong thing.

    -Paul

Tom Carstens

unread,
Mar 20, 2012, 2:07:31 PM3/20/12
to golan...@googlegroups.com
Please name one time a stable library changed the return type of a function and didn't notify users that it was going to happen. If you can do that then I'll accept that it's something that might be detrimental, until then you're talking about strange and mystical possibilities in the real of scifi.

I still insist that there isn't a trade off. It's easy to read and it lends extra functionality. There's apparently a small chance that a third-party library could up and move on you for no reason, but that's the kind of thing that's so minor that it doesn't even bother noting (as most language features suffer from similar problems.)

Tom Carstens

unread,
Mar 20, 2012, 2:12:42 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
Yay, finally a reason I can understand. So then, since os.Exit() doesn't insure that defers are called, how do you exit from a program if a fatal error has occurred?

Kyle Lemons

unread,
Mar 20, 2012, 2:16:56 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
Panic.  Or let the errors propagate back up the chain to main.

Kyle Lemons

unread,
Mar 20, 2012, 2:20:23 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
On Tue, Mar 20, 2012 at 11:04 AM, Tom Carstens <tomca...@gmail.com> wrote:
I'm not trying to be stubborn here, I truly am trying to write code that other people have an easier time with. However, I also don't want to write more than I have to. 

One great thing about editors and muscle memory are their ability to automate and parameterize repetitive coding tasks.
 
Please note that I was patterning check after assert to see if it was more legible, there's one more feature that I require that's in my original assert.Check(). Namely, I need a mute button. log doesn't have one. Which means I need another if statement, I can choose to do it around my call to log (which means there's an addition 4 lines per log, possibly as low as 2 in corner cases) or I can embed it in check.

log.SetOutput(ioutil.Discard)
 
That said, you have a really verbose error message style, that I hope is not standard (and I don't think I'll ever create something that would please you.)

I was using your example.  I wouldn't normally say something like "unacceptable error from <call>" but I often do say thins like ("pkg: read(%q): %s", filename, err) or ("pkg: after %d tries: %s", try, err).  Formatting is important in error messages, and passing a string doesn't cut it.

Tom Carstens

unread,
Mar 20, 2012, 2:23:06 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
Panic it is then. Unless you can tell me why I'd want to propagate errors back to main and violate the abstractions I'm using (e.g. if I wanted main to do it I'd put it in main.)

John Asmuth

unread,
Mar 20, 2012, 2:36:28 PM3/20/12
to golan...@googlegroups.com


On Tuesday, March 20, 2012 2:07:31 PM UTC-4, Tom Carstens wrote:
Please name one time a stable library changed the return type of a function and didn't notify users that it was going to happen. If you can do that then I'll accept that it's something that might be detrimental, until then you're talking about strange and mystical possibilities in the real of scifi.

If only I could limit all of my future coding to use only stable and well tested libraries, and avoid completely the junk that the guy in the next cubicle wrote over the weekend.

If it can be done with a language, someone has already done it and someone will do it in the future. Calling it mystical and scifi is kidding yourself.

andrey mirtchovski

unread,
Mar 20, 2012, 2:43:00 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
> Please name one time a stable library changed the return type of a function
> and didn't notify users that it was going to happen. If you can do that then
> I'll accept that it's something that might be detrimental, until then you're
> talking about strange and mystical possibilities in the real of scifi.

when strings.Split(_, _, N) became strings.Split(_, _) and
strings.SplitN() was introduced, the notification to the list was
buried in a weekly release documentation that was easily two pages
long. people kept asking about getting a "wrong number of arguments"
error on go-nuts all the way from r57 to r59 or thereabouts.

it happens. all code is a moving target, including compiled binaries :)

andrey mirtchovski

unread,
Mar 20, 2012, 2:48:54 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
> when strings.Split(_, _, N) became strings.Split(_, _) and
> strings.SplitN() was introduced, the notification to the list was
> buried in a weekly release documentation that was easily two pages
> long. people kept asking about getting a "wrong number of arguments"
> error on go-nuts all the way from r57 to r59 or thereabouts.

oh, and it appears that the corresponding change to bytes.Split() was
not mentioned at all. (weekly.2011-07-07, for those keeping track of
the bikeshedding progress.)

Tom Carstens

unread,
Mar 20, 2012, 5:17:12 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
That's all I needed. I'll accept that there is a trade off with mixing multi-return functions, namely that it becomes slightly harder to debug changes in a library function used.
Of course, that's not to say I don't find it acceptable. It would be a fairly simple matter of converting it into the style currently required (which may defeat the purpose to some extent, but the main reason I wanted it is because its something that would come naturally (i.e. first run implementation.)) And that's only necessary if there's no documentation on the change.
So here's more or less the objective pros and cons (in fact there's only one of each with some grey area):

Pros More flexible abstraction by providing the same power to multi-return functions as their single-return siblings or in my additional notation version multi-returns get treated like slices
Con: Makes debuging more difficult
Grey: Some people like the syntax and others don't, more symmetric (not everyone cares,) potentially additional notation

To remind you in case you missed it what I wanted is something like this:

func foo() int { return 1}
func bar() (int, int) { return 2, 3 }
func baz(a,b,c int) int { return a*b+c }

baz(foo(),bar()...) //returns 5
baz(bar()..., foo()) //returns 7

Since slices can be split in function calls by using the ... operator. I simply want multiple returns to be split-able with it too. This is of course syntactic sugar for the way you normally use multiple-returns, but it gives you the benefit of not needing to know about values that you have nothing to do with (the return of bar isn't really interesting in the above example, so we directly pass it and thus don't need to know about it.)

Tom Carstens

unread,
Mar 20, 2012, 5:23:56 PM3/20/12
to golan...@googlegroups.com
See but even with exaggerating my opinion I still couldn't get you to back up your statements. That's all I needed is evidence which aam provided.

I'd really be interested in what burned you so badly that you've decided that users of a language shouldn't be presented with features that are exploitable. Since you seem to have some ill will against proposed new features that carry risk (as all features do) simply because it isn't tried and tested. Maybe go isn't the language for it (after all go isn't about experimental features) but there's no need to be hostile to discussion of such features (because not presenting evidence and flat out rejecting a proposed new feature is being hostile to it, in order for a feature to be determined good or bad you'll need some empirical evidence even if you have some theoretical reason to dislike it (theory is often wrong.))

Rémy Oudompheng

unread,
Mar 20, 2012, 5:25:08 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
Le 20 mars 2012 22:17, Tom Carstens <tomca...@gmail.com> a écrit :
> To remind you in case you missed it what I wanted is something like this:
>
> func foo() int { return 1}
> func bar() (int, int) { return 2, 3 }
> func baz(a,b,c int) int { return a*b+c }
>
> baz(foo(),bar()...) //returns 5
> baz(bar()..., foo()) //returns 7
>
> Since slices can be split in function calls by using the ... operator. I
> simply want multiple returns to be split-able with it too. This is of course
> syntactic sugar for the way you normally use multiple-returns, but it gives
> you the benefit of not needing to know about values that you have nothing to
> do with (the return of bar isn't really interesting in the above example, so
> we directly pass it and thus don't need to know about it.)

I feel that naming function arguments appropriately increases code readability.

Rémy.

Tom Carstens

unread,
Mar 20, 2012, 5:27:26 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
Well yes... but I'm not presenting functional code. I'm presenting a syntax idea that happens to require a bit of code to explain it. foo, bar, and baz aren't intended to be useful or readable, all they are are function templates necessary to present the syntax.

Andrew Gerrand

unread,
Mar 20, 2012, 6:17:58 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
Here you have saved a couple of lines of code but have you actually saved yourself any effort? You introduced another function call that obscures what's going on, and a type assertion that adds noise. Furthermore your assert function won't generally provide a nice error message. 

Use log.Fatal and you can save writing os.Exit over and over.  

Don't fight the language, it'll just make your code hard to read. 

Andrew

On Wednesday, March 21, 2012, Tom Carstens wrote:
wait I can use interface{}? um... one sec let me change that (I don't do too much with interfaces so I really didn't know that worked.)

I understand your point but when you have :

file, err := os.Open("some file")
if err != nil {
    fmt.Println("this prog: ", err)
    os.Exit(1)
}
defer file.Close()

Written a dozen times in a few different files it just starts becoming a pain. Whereas in my project I can do this:
file = assert.Check(os.Open("some file")).(*os.File)
defer file.Close()

I really wish the type assertion wasn't necessary but unless generics are added it'll be impossible to remove. Further, I like that assert.Check() because it also works for strconv functions or any other error prone act.

On Tuesday, March 20, 2012 11:11:25 AM UTC-5, Jan Mercl wrote:
On Tuesday, March 20, 2012 4:57:17 PM UTC+1, Tom Carstens wrote:
I disagree. Rewriting the same lines over and over is a waste of time. The assert.Check() function I provided above saved me 76 lines of code on a project I'm only 10% done with. Further more, with my addition I'd save about double that. Saving nearly 1.5 kloc isn't something to scoff at. And this is only talking about error handling, I'm sure I could engineer plenty of other places to use such a feature to save redundant typing.

The savings are probably temporary only. Once you will have to return to your code a year later (or anyone else just now), it IMO wastes reading time (more precious, code is more often read than written). Anyone will have [again] figure out why is the nice and short type literal 'interface{}' [re]named to 'value' for no good reason (sans the worsened reading). The same or similar applies to "centralized" error handling and/or assert.Check (CheckEquals, ...). It makes simple things/logic visible in a single line/few lines of code go somewhere else/away. The reading/comprehending context is lost. IMO the total "gain" is a negative value not in the near future but right now.

Even that being said, it's no critique of your coding style. It's just an explanation why I don't do such things (and I used to do them before I got burnt ;-)

Tom Carstens

unread,
Mar 20, 2012, 6:20:06 PM3/20/12
to golan...@googlegroups.com, Tom Carstens
To be perfectly frank, I hadn't looked at log until it was brought up. I've only been coding in Go for a few weeks.

John Asmuth

unread,
Mar 20, 2012, 7:20:41 PM3/20/12
to golan...@googlegroups.com


On Tuesday, March 20, 2012 5:23:56 PM UTC-4, Tom Carstens wrote:
See but even with exaggerating my opinion I still couldn't get you to back up your statements. That's all I needed is evidence which aam provided.

Even if he didn't provide evidence, your assertion that it never could happen was a bit out there, so I didn't bother.
 
I'd really be interested in what burned you so badly that you've decided that users of a language shouldn't be presented with features that are exploitable.

C++.
 
Since you seem to have some ill will against proposed new features that carry risk (as all features do) simply because it isn't tried and tested.

You are conflating "new features that carry risk" with "features whose risks don't outweigh their absolutely trivial benefits".
 
Maybe go isn't the language for it (after all go isn't about experimental features) but there's no need to be hostile to discussion of such features (because not presenting evidence and flat out rejecting a proposed new feature is being hostile to it, in order for a feature to be determined good or bad you'll need some empirical evidence even if you have some theoretical reason to dislike it (theory is often wrong.))

On the other hand, I felt like you were pretty hostile to my explanations about why I thought it was a good idea to keep things the same.

This conversation has taken a turn for the silly, so I'm going to stop replying.

Nigel Tao

unread,
Mar 20, 2012, 7:43:16 PM3/20/12
to Tom Carstens, golan...@googlegroups.com
On 21 March 2012 03:49, Tom Carstens <tomca...@gmail.com> wrote:
> Another example:
>
> _, err1 := file1.Read(buf1)
> _, err2 := file2.Read(buf2)
> if err1 != nil || err2 != nil {
> if err1 == nil {
> if !*silent {
> fmt.Printf("%s on %s\n", err2, file2.Name())
> }
> } else if err2 == nil {
> if !*silent {
> fmt.Printf("%s on %s\n", err1, file1.Name())
> }
> }
> os.Exit(1)
> }

Idiomatic would be:

if _, err := file1.Read(buf1); err != nil {
log.Fatalf("could not read %q: %v", file1.Name(), err)
}
if _, err := file2.Read(buf2); err != nil {
log.Fatalf("could not read %q: %v", file2.Name(), err)
}

Mikael Gustavsson

unread,
Mar 20, 2012, 10:45:16 PM3/20/12
to golan...@googlegroups.com
Just to point it out, Go might be a simple language - but it does have some very powerful concepts such as closures and reflection which makes it possible to do all kinds of magic things if you really want to.

In this case you can for example manually simulate currying using closures:

func filecheck(prefix string) func(*os.File, error) *os.File {
return func(file *os.File, err error) *os.File {
if err != nil { log.Panic(prefix, err) }
return file
}
}

func main() {
file := filecheck("testing")(os.Open("test"))
defer file.Close()
}

On Tuesday, March 20, 2012 2:38:10 PM UTC+8, Tom Carstens wrote:
Okay... so it works that way. However, it doesn't work if I change it
slightly. (as a note, i'm trying to write a centralized error handling
function so I'm actually passing in a bit more data.)

so how about this then:
http://play.golang.org/p/dh0h1pYftn

On Mar 20, 12:03 am, Tarmigan <tarmigan+gol...@gmail.com> wrote:
> Did you try it?http://play.golang.org/p/T6WIcT_8q5
>
> -Tarmigan
>
>
>
>
>
>
>
> On Mon, Mar 19, 2012 at 7:43 PM, Tom Carstens <tomcarst...@gmail.com> wrote:
> > So what I'm trying to do is something like this:
>
> > func function1(a,b int) {...}
> > func function2(a int) (int, int) {...}
> > ...
> >    function1(function2(1))
>
> > So is it possible to directly pass a function that returns multiple
> > values into another one? Or do I have to set the returns to something
> > and then pass them in?
Reply all
Reply to author
Forward
0 new messages