About error "f() used as value" - why is this good?

4,105 views
Skip to first unread message

Dmitri Shuralyov

unread,
May 29, 2013, 6:56:38 PM5/29/13
to golan...@googlegroups.com
Does anyone think this small inconsistency is somewhat unfortunate?

package main

import "fmt"

func main() {
// Ok: Functions that return 0, 1, 2, ... values
f0 := func() {}
f1 := func() (string) { return "1" }
f2 := func() (string, string) { return "1", "2" }

// func Println(a ...interface{}) (n int, err error)

// Ok: Function that accepts 0, 1, 2, ... values
fmt.Println()
fmt.Println("1")
fmt.Println("1", "2")

// Not Ok: 0 values
// Ok: 1, 2, ... values
fmt.Println(f0()) // Error: f0() used as value
fmt.Println(f1())
fmt.Println(f2())
}


Or are there good reasons for this decision in the language spec? I'd like to know, because I can't think of one.

Jan Mercl

unread,
May 29, 2013, 7:01:59 PM5/29/13
to Dmitri Shuralyov, golang-nuts

It seems to me that you've forgotten to say where or what is the problem. I fail to find it.

-j

Gustavo Niemeyer

unread,
May 29, 2013, 7:06:20 PM5/29/13
to Dmitri Shuralyov, golan...@googlegroups.com
On Wed, May 29, 2013 at 7:56 PM, Dmitri Shuralyov <shur...@gmail.com> wrote:
> // Not Ok: 0 values
> // Ok: 1, 2, ... values
> fmt.Println(f0()) // Error: f0() used as value
(...)
> Or are there good reasons for this decision in the language spec?

You're asking to print something that simply doesn't exist. f0 doesn't
return anything, so there's no reasonable outcome out of what that
code is trying to do.


gustavo @ http://niemeyer.net

Dmitri Shuralyov

unread,
May 29, 2013, 7:20:20 PM5/29/13
to Gustavo Niemeyer, golan...@googlegroups.com
You're asking to print something that simply doesn't exist. f0 doesn't
return anything, so there's no reasonable outcome out of what that
code is trying to do.

That's not true. Println() prints its operands, separated by spaces, and appends a newline. fmt.Print() is valid Go code that prints a newline, despite there being no operands.

I can imagine fmt.Println(f0()) behaving exactly like fmt.Println() does (except, f0() is executed first, and then fmt.Println()).

---

I'm not a language design expert (but I'd like to learn more about the topic, hence I'm posting the question here), but to me, a hypothetical language Go' where the above program compiles without error seems simpler than Go.

One of the following two options must be true:

1. Either that hypothesis is wrong, and in fact the Go' language where the above program compiles without error is actually more complex than the existing Go language (with the error above). If so, can someone please explain why that's the case?

2. Or, the hypothesis is right and Go' is indeed simpler, but there are other reasons why the added complexity of having the above-mentioned error included is deemed to be worth it. In which case, I'd also like to know what the reasons are.

Thank you.

Martin Schnabel

unread,
May 29, 2013, 7:24:15 PM5/29/13
to golan...@googlegroups.com
On 05/30/2013 01:20 AM, Dmitri Shuralyov wrote:
> You're asking to print something that simply doesn't exist. f0 doesn't
>
> return anything, so there's no reasonable outcome out of what that
>
> code is trying to do.
>
>
> That's not true. Println() prints its operands, separated by spaces, and
> appends a newline. fmt.Print() is valid Go code that prints a newline,
> despite there being no operands.
>
> I can imagine fmt.Println(f0()) behaving exactly like fmt.Println() does
> (except, f0() is executed first, and then fmt.Println()).
http://play.golang.org/p/KDF8JVKcOr ?

Gustavo Niemeyer

unread,
May 29, 2013, 7:49:49 PM5/29/13
to Dmitri Shuralyov, golan...@googlegroups.com
On Wed, May 29, 2013 at 8:20 PM, Dmitri Shuralyov <shur...@gmail.com> wrote:
>> You're asking to print something that simply doesn't exist. f0 doesn't
>> return anything, so there's no reasonable outcome out of what that
>> code is trying to do.
>
> That's not true. Println() prints its operands, separated by spaces, and
> appends a newline. fmt.Print() is valid Go code that prints a newline,
> despite there being no operands.

If you have an empty glass, you cannot drink what is inside, or boil
it, or do anything else with it, because it does not exist.

The result of f0() does not exist. You cannot print it, or provide it
as a parameter to anything, or assign it to a variable, or do anything
else with it, because it simply does not exist.


gustavo @ http://niemeyer.net

Ian Lance Taylor

unread,
May 29, 2013, 7:52:18 PM5/29/13
to Dmitri Shuralyov, Gustavo Niemeyer, golan...@googlegroups.com
On Wed, May 29, 2013 at 4:20 PM, Dmitri Shuralyov <shur...@gmail.com> wrote:
>
> I'm not a language design expert (but I'd like to learn more about the
> topic, hence I'm posting the question here), but to me, a hypothetical
> language Go' where the above program compiles without error seems simpler
> than Go.
>
> One of the following two options must be true:
>
> 1. Either that hypothesis is wrong, and in fact the Go' language where the
> above program compiles without error is actually more complex than the
> existing Go language (with the error above). If so, can someone please
> explain why that's the case?
>
> 2. Or, the hypothesis is right and Go' is indeed simpler, but there are
> other reasons why the added complexity of having the above-mentioned error
> included is deemed to be worth it. In which case, I'd also like to know what
> the reasons are.

The part of the spec that invalidates your example is the phrase "and
g must have at least one return value" in the section
http://golang.org/ref/spec#Calls . This is there specifically to
disallow code like this:

func g() {}
func f() {}
func h() {
f(g())
}

This code appears to pass the result parameters of g() as the
arguments to f(), but it is actually just confusing.

So what you are suggesting is that we add a further exception to the
spec. At the end of that paragraph in the spec, we could say "in this
case g is permitted to have no return values."

Based on that, I'm going with your option 1: supporting this case
would make the Go language (slightly) more complicated.

Ian

Dmitri Shuralyov

unread,
May 29, 2013, 8:19:01 PM5/29/13
to golan...@googlegroups.com, Dmitri Shuralyov, Gustavo Niemeyer
mb0, that's a neat way to go around the type system.

Ian, thanks, your post was insightful.

So what you are suggesting is that we add a further exception to the 
spec.  At the end of that paragraph in the spec, we could say "in this 
case g is permitted to have no return values."

Actually, what I was suggesting is to remove the "and g must have at least one return value" exception, i.e.:

The call of f must contain no parameters other than the call of g, and g must have at least one return value. If f has a final ... parameter, it is assigned the return values of g that remain after assignment of regular parameters.

Could become more simply:

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.

Which is simpler. However, that would permit the code you pointed out.

func g() {} 
func f() {} 
func h() { 
    f(g()) 
} 

I can see there may be some value in disallowing that.

Is that the only reason for the "and g must have at least one return value" clause or are there more?

Ian Lance Taylor

unread,
May 29, 2013, 8:37:45 PM5/29/13
to Dmitri Shuralyov, golan...@googlegroups.com, Gustavo Niemeyer
On Wed, May 29, 2013 at 5:19 PM, Dmitri Shuralyov <shur...@gmail.com> wrote:
>
> Which is simpler. However, that would permit the code you pointed out.
>
> func g() {}
> func f() {}
> func h() {
> f(g())
> }
>
> I can see there may be some value in disallowing that.
>
> Is that the only reason for the "and g must have at least one return value"
> clause or are there more?

That's the only reason I recall. I remember discussing it when we
added the ability to write f(g()) when g has multiple result
parameters. For that it's worth, I'm pretty sure that preceded the
current notion of variadic functions.

Ian

Gustavo Niemeyer

unread,
May 29, 2013, 10:00:17 PM5/29/13
to Ian Lance Taylor, Dmitri Shuralyov, golan...@googlegroups.com
The idea of allowing nothing to be used as a value is flawed in several fronts..

fmt.Sprintf("%s", a(), b(), c(), d(), e()) => "Guess who I am!"


gustavo @ http://niemeyer.net

Jesse McNelis

unread,
May 29, 2013, 10:05:49 PM5/29/13
to Gustavo Niemeyer, Ian Lance Taylor, Dmitri Shuralyov, golang-nuts
On Thu, May 30, 2013 at 12:00 PM, Gustavo Niemeyer <gus...@niemeyer.net> wrote:
The idea of allowing nothing to be used as a value is flawed in several fronts..

    fmt.Sprintf("%s", a(), b(), c(), d(), e()) => "Guess who I am!"

and that's basically the same as:
a := []byte{a(),b(),c(),d(),e()}  //what is the len() of a?
which is quite a bit more scary.

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

Dan Kortschak

unread,
May 29, 2013, 10:19:55 PM5/29/13
to Jesse McNelis, Gustavo Niemeyer, Ian Lance Taylor, Dmitri Shuralyov, golang-nuts
On Thu, 2013-05-30 at 12:05 +1000, Jesse McNelis wrote:
> and that's basically the same as:
> a := []byte{a(),b(),c(),d(),e()} //what is the len() of a?
> which is quite a bit more scary.

Except that non-unity result parameter lists are not allowed in literal
[]T assignments, whereas they are in function calls.

Martin Schnabel

unread,
May 30, 2013, 6:02:00 AM5/30/13
to golan...@googlegroups.com
On 05/30/2013 02:19 AM, Dmitri Shuralyov wrote:
> mb0, that's a neat way to go around the type system.
>
i actually meant to write this http://play.golang.org/p/eN38hsTrCH
to highlight the variadic nature of Println.
i understand an empty fmt.Println() as a fmt.Println(nil...) with an
empty slice as parameter.

Jan Mercl

unread,
May 30, 2013, 8:07:02 AM5/30/13
to Dmitri Shuralyov, golang-nuts
On Thu, May 30, 2013 at 1:01 AM, Jan Mercl <0xj...@gmail.com> wrote:
> It seems to me that you've forgotten to say where or what is the problem. I
> fail to find it.

Ok, even without getting any answer, from what others I infer what you
probably mean. If that's the case then given:

func foo() {}

this must be valid as well for you "problem" case:

a := foo()

I believe you don't expect the above statement to be legal. Then

fmt.Println(foo())

is only the exactly very same "problem". That's why I didn't even
consider that you're talking about it and was not able to see the
"problem".

-j

Dmitri Shuralyov

unread,
May 30, 2013, 11:34:16 AM5/30/13
to golan...@googlegroups.com
I am primarily interested in the case of making variadic functions accept input from funcs that return 0 values.

// I think it'd be nice if this was valid
func g() {}
func f(...int i) {}
func h() {
    f(g()) 
}

The problem is that to make it work only in that case, the spec would have to be made slightly more complex by adding an exception, as Ian pointed out previously:

So what you are suggesting is that we add a further exception to the
spec.  At the end of that paragraph in the spec, we could say "in [the case for variadic functions]
g is permitted to have no return values."

I am more indifferent towards making this case work:

// Not sure if this is good or bad
func g() {}
func f() {}
func h() {
    f(g()) 
}

Making both work can be made by simplifying the spec and removing the "and g must have at least one return value" exception.

However, most other counter-examples that were posted here would remain invalid code in either case.

func foo() {} 
a := foo() // Error: assignment count mismatch: 1 = 0

// Single-value context, so all funcs are required to return exactly one value (not zero, not two, not three, etc.)
a := []byte{a(), b(), c(), d(), e()} // Therefore len(a) must be 5

// Single-value context, so all funcs are required to return exactly one value (not zero, not two, not three, etc.)
fmt.Sprintf("%s", a(), b(), c(), d(), e()) => "Guess who I am!" // Hypothetical error: zero-value a() in single-value context (actual error: a() used as value)
fmt.Sprintf("%s%s%s%s%s%s%s%s%s%s", a(), b(), c(), d(), e()) => "Guess who I am!" // Error: multiple-value a() in single-value context

Ziad Hatahet

unread,
May 30, 2013, 1:55:43 PM5/30/13
to Dmitri Shuralyov, Ian Lance Taylor, golan...@googlegroups.com
Languages like Scala and Rust allow what Dimitri mentioned in the original post, while disallowing the code sample that Ian listed.

Take for instance:

def foo() {
  println("foo")
}

def bar(): Unit = { // This is a more verbose way of defining "foo()" above.
  println("bar")    // Note the Unit return type above.
}

def main(args: Array[String]) {
  println("value is: " + foo()) // Prints: "value is: ()"
  println(foo(bar()) // error: too many arguments for method foo: ()Unit
}


Hence, the goals are not mutually exclusive.

--
Ziad


Reply all
Reply to author
Forward
0 new messages