Help us understand the problem. What is going on with this article?

golangの関数をまとめてみた。

More than 3 years have passed since last update.

関数とは

関数は一連の動作の集まり!わからない人は下に書いてる例を見ればわかるはず!
例として出す関数はすごーく簡単なものだけにしています。理由としては読むのが面倒な例は考えるのも面倒!

書式

func
fucn 関数名(パラメーター [,...複数可]) 戻り値の型[,...複数可] {
    処理
}

以下が実際の例になります

func
func add(atai1 int, atai2 int) int {
    return atai1 + atai2
}

add(1, 2) // add関数使う

こんな感じに関数は、処理をまとめておくことが出来ます。
では、ここからは特徴的なところをまとめていこうと思います。

golangで出来る記法

戻り値の変数名を指定できる

func
func add(atai1 int, atai2 int) (ans int) {
    ans = atai1 + atai2
    return
}

自分はそんなにすごいたくさの言語を触っているわけではありませんが、自分はgoで初めて指定できる系の関数を見ました。まだ本格的に運用したことないので、わかりませんが戻り値の統一する時などに使える?かもしれない。

複数の戻り値を簡単に指定できる

func
func main() {
    add, sub := calc(1, 2)
    fmt.Println(add)    // 3
    fmt.Println(sub)    // -1
}

func calc(atai1 int, atai2 int) (add int, sub int) {
    add = atai1 + atai2
    sub = atai1 - atai2
    return
}

自分的には、わりとこれが衝撃的だったのですが、関数で簡単に複数値を返せるのはなかなか嬉しい。他の言語だと、配列などでワンクッション必要だったりと正直面倒だったりする挙動が一瞬で出来る。
もちろんですが、変数名の指定もできます。(複数になるとわりとメリットを感じる。)

ちなみにgolangのこの複数戻り値を指定するパターンはエラー処理をするためによく使います。

func
package main

import (
    "fmt"
    "os"
    "errors"
)

func main() {
    ans, err := calc(0, 2)  // エラーにする
    if err != nil {
        fmt.Fprintf(os.Stderr, "エラー:%d", err)
        os.Exit(1)
    }
    fmt.Println(ans)
}

func calc(atai1 int, atai2 int) (ans int ,err error) {
    if atai1 == 0 || atai2 == 0 {
        return 0, errors.New("不正な数値が設定されているよ")
    }
    ans = atai1 + atai2
    return ans, nil
}

上記のように、エラーメッセージの入った変数を同時に返すことで、簡単にエラーの検知ができる。いわゆるgolangっぽい書き方になる。しかも、この複数変数を返す時は、受け取り側で正しくどちらも受け取るように処理を書かないとエラーになるので、エラー処理を忘れることもなくなるので、すごく優れもの!
エラーの書き方については深堀するとなかなかのボリュームなので、また別の記事で取り上げてみたいと思います。

再帰処理ができる

よくあるフィボナッチ数を出すという再帰処理をgolangで書くとこんな感じになる。

func
func fib(n int) int {
    if n < 2 {
        return n
    }
    return fib(n-2) + fib(n-1)
}

func main() {
    fmt.Println(fib(10))
}

注意点があります。golangでは再帰処理は可能ですが、基本的に向いてないっぽいのでやめた方がいいです。理由を詳しく知りたい方はこちらを読むと理由がよくわかります!

可変個引数関数

可変個引数とは、名前の通りで引数の数を自由に設定できるというものです。fmtパッケージにあるPrintln関数も可変個引数関数で出来ています。よって、以下のようなことが可能になります。

func
func main() {
    a := "aiueo"
    b := "kakikukeko"
    fmt.Println(a, b)   // aiueo kakikukeko
}

この関数を実際に書くと以下のようになります。

func
func main() {
    fmt.Println(add(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)) // 55
}

func add(atais ...int) int {
    ans := 0
    for _, atai := range atais {
        ans += atai
    }
    return ans
}

上記は与えられた値を全て足し算する関数になります。この記法を使うことで、汎用的な関数を作ることができそうです。この可変長引数は暗黙的に値を配列にコピーして関数全体のスライスに渡しています。もし、引数にスライスをそのまま入れたい時は、以下のように書くことも可能です。
ちなみにこの関数に渡されるスライスは通常のスライスと違うようです。

func
atais := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
fmt.Println(add(atais...))

関数値

golangでは、関数はファーストクラス値です。つまり、関数は他の値と同様で型を持ちます。変数に代入したり、関数へ渡したり、関数から返すことも出来ます。

func
func main() {
    addFunc := add
    fmt.Println(addFunc(1, 2))  // 3
}

func add(atai1 int, atai2 int) int {
    return atai1 + atai2
}

関数に関数を渡す場合、以下のようなことも出来ます。

func
func main() {
    ans1 := calc(10, 5, add)
    fmt.Println(ans1)   // 15

    ans2 := calc(10, 5, sub)
    fmt.Println(ans2)   // 5
}

func calc(atai1 int, atai2 int, calcPattern func(int, int) int) int {
    return  calcPattern(atai1, atai2)
}

func add(atai1 int, atai2 int) int {
    return atai1 + atai2
}

func sub(atai1 int, atai2 int) int {
    return atai1 - atai2
}

渡された関数によって中の処理が変わるような。途中まで同じなんだけど、一部だけ違う!っていう場合とか結構使えますね。

無名関数

無名関数とは、そのままで名前がない関数です。名前がない関数ってなんやねんって話なんですが、ちゃんとメリットがあります。関数を囲むスコープ範囲の全体へアクセスが出来るというところです。

func
func main() {
    greetFunc := greet()
    fmt.Println(greetFunc())    // こんばんは山田太郎ですこの挨拶は1回目の挨拶です
    fmt.Println(greetFunc())    // こんばんは山田太郎ですこの挨拶は2回目の挨拶です
    fmt.Println(greetFunc())    // こんばんは山田太郎ですこの挨拶は3回目の挨拶です
    fmt.Println(greetFunc())    // こんばんは山田太郎ですこの挨拶は4回目の挨拶です
}

func greet() func() string {
    var count int
    return func() string {
        count++
        return fmt.Sprintf("こんばんは山田太郎ですこの挨拶は%d回目の挨拶です", count)
    }
}

自分が保持していないcountに対してアクセスすると同時に、値をキープしていることがわかります。ここで一つ面白いことがわかります。普通、スコープの範囲はそのブロックの範囲で決まりますが、このような無名関数を使ったいわゆるクロージャという手法を使うと、スコープ範囲が限定できなくなる。(greetの中にあるcountという変数がmainに戻っても値を保持しているため)

遅延関数呼び出し

関数の処理を実行する上で、関数が終わったと同時に必ず実行されてほしい処理があります。一番よく目にするパターンで、httpリクエストを実行した時にネットワークに接続を必ず行います。その接続を関数処理実行が終わった時に、接続を切るという処理を書く必要があります。
この例を書くとかなり長くなってしまうのですが、書いてみます。

func
func Get(url string) (*Page, error) {

    page := &Page{}
    resp, err := http.Get(url)
    if err != nil {
        return nil, errors.New("存在しないか、サーバが止まっているよ")
    }
    defer resp.Body.Close() // 関数の処理が終わった時にリソース解放

    doc, err := html.Parse(resp.Body)
    if err != nil {
        return nil, errors.New("パースできなかったよ")
    }
    var f func(*html.Node)
    f = func(n *html.Node) {
        if n.Type == html.ElementNode && n.Data == "title" {
            page.Title = n.FirstChild.Data
        }

        if n.Type == html.ElementNode && n.Data == "meta" {
            if isDescription(n.Attr) {
                for _, attr := range n.Attr {
                    if isContent(attr) {
                        page.Description = attr.Val
                    }
                }
            }

        }

        for c := n.FirstChild; c != nil; c = c.NextSibling {
            f(c)
        }
    }
    f(doc)

    return page, nil
}

細かいところは置いといて、resp.Body.Close()というコードに注目してください。これはネットワークを切ることを保証しますという命令となります。これを実行したいタイミングはこの関数が終了する時です。終了する時のパターンにはいくつか種類があります。

  • 何か失敗した。
  • 正常に終了した。

例えば、これがもっと複雑な関数になって、エラーが発生する可能性があるタイミングが混在したらどうでしょう?resp.Body.Close()を毎回書くことになります!だるい!
そんな時に、deferが役に立ちます。これを処理の前に書くと関数が終了するタイミングに実行してくれます。deferは複数書くことも可能で、評価される順番は遅延された順序の逆順です。つまり、下に書かれたdeferが先に実行されます。これを使うタイミングはファイルのロックやアンロックなど、リソース解放でよく使うので、覚えておくと非常に便利です。
deferは便利ですが、それ相応の注意が必要な点も存在します。詳しくはまた別記事で書こうと思います。

パニック、リカバー

golangはコンパイル時に、コードのエラーなどを検出してくれますが、実行時エラーというのも起きることがあります。そのようなエラーのことをgolangではパニックと呼んでいます。また、そのパニックが発生した際にそのまま終わるのではなく、後処理などを行うリカバーという処理があります。
これは重大なエラーなどに使うべきであって、通常の予期できるエラーはというのはpanicを使う必要はないと思います。また、ここで書くと分量が多くなるので、別記事に書こうと思います。

golangにはないもの

デフォルトパラメータの考えがない

func
// swiftです!!!!
func add(atai1: Int, atai2: Int = 2) -> Int {
    return atai1 + atai2
}

Swiftという別言語では、デフォルトパラメータという考えがあります。
上記のようにatai2がもし、何も引数として指定されなかったら2を入れますという動きのことです。
そもそも、渡さない可能性のある引数ありきの設計はだめ!っていうことなんでしょうかね。

引数名での指定がない

デフォルトパラメータという考えがないので、引数名の指定の必要がなくなるので、以下のように指定する書式もありません。

func
// swiftです!!!!
func add(atai1: Int, atai2: Int = 2) -> Int {
    return atai1 + atai2
}

add(1, atai2: 2)

なかなかの分量になりました。深堀するともっと色々書くことがありますが、今回はここまでにしたいと思います。

halprogramming
専門学校HALのプログラミングが好きな人が集まっている同好会です。HALの在学生に限らず、OB, OGなども存在します。
https://www.hal.ac.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away