恥ずかしながら、golangの多値を返す関数の戻り値を引数に渡した場合の挙動を把握していなかった

はじめに

通常の言語だと、多値とdestructuring(分配束縛)は別物で、単一の構造体をバラす構文が入っているという感じのことが多い(schemeなどではそれとは全く別に多値を返す事ができるけれど。一方でbindingのための構文が別途用意されている)。例えばpythonの例でいうと以下の様な感じ。

vs = (1, 2)
x, y = vs

単一のタプルをdestructuringした結果、それぞれの値が変数に束縛されるという形。 これがgolangでも同様だと思っていたのだけれど。実はちょっと違ったという話。

ふつうの分配束縛(のように見える)

golangでも多値を返す関数が定義できる(pair)。そしてpythonの例と同様の理屈でdestructuringが行われ、複数の引数を取る関数(add)に渡され使われる(ように見えていた)。

package main

import "fmt"

func pair(x int) (int, int) {
    return x, x + 1
}

func add(x int, y int) int {
    return x + y
}

func main() {
    x, y := pair(10)
    fmt.Println(add(x, y))
}

実は

実は、多値を返す関数の戻り値をそのまま複数関数に渡すことができる。こういう感じに。知らなかった。

package main

import "fmt"

func pair(x int) (int, int) {
    return x, x + 1
}

func add(x int, y int) int {
    return x + y
}

func main() {
    fmt.Println(add(pair(10)))
}

addはpairの呼び出しをそのまま引数として受け取っている。

よく考えてみれば

よく考えてみれば、たまに fmt.Println などを使う折に触れていた。 fmt.Println(parseFoo(v)) みたいなエラー値も含む戻り値の関数の呼び出しでも気にせずprintデバッグしていたような気がする。

実は仕様にも

実は仕様にもしっかり明記されている。

https://golang.org/ref/spec#Calls

As a special case, if the return values 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, 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.

何が嬉しいか

何が嬉しいかというと。エラー値を返す関数とpanicする関数の定義の扱いが便利になる。例えば通常の用途ではエラー値も返すParseFooを使い、テストや自明なところでは、エラーになったらpanicするMustFooを使うようにするという部分を少しの変更で対応できる。わざわざMustFooを定義する必要はなくMustだけを定義すれば良い。

package main

import (
    "errors"
    "fmt"
)

// ParsePositive :
func ParsePositive(n int) (int, error) {
    // 通常は文字列などかもしれない
    if n >= 0 {
        return n, nil
    }
    return n, errors.New("not positive")
}

// Must :
func Must(n int, err error) int {
    if err != nil {
        panic(err)
    }
    return n
}

func main() {
    n := Must(ParsePositive(10))
    fmt.Println("for test ", n)
}

Must(ParsePositive(10)) みたいな形ですむ。便利。知らなかった。