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
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)
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())becomesbaz(foo())(bar.ret1)(bar.ret2))andbaz(bar(), foo())becomesbaz(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.
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
Or worse: the compiler doesn't report an error (Foo() takes a variadic
argument) and your code silently breaks.
Fails its tests?
> silently breaks.
Maybe.
--
Chris "allusive" Dollin
... 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.
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 ...
> and you have to check 10 different places
Or the place where the error is spotted ...
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.
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.
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
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.
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.
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.
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
_, 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)
}
file = assert.Check(os.Open("some file")).(*os.File)defer file.Close()
However, the big improvement is not in sensibility, but in symmetry.
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
It's an example of the NJ vs MIT approach: http://www.jwz.org/doc/worse-is-better.html
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?
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.)
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.)
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 :)
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.)
I feel that naming function arguments appropriately increases code readability.
Rémy.
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 ;-)
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.))
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)
}
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?