何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『プログラミング言語Go』読書記録 その1

第二言語を学ぶ時期が来た

以前、勉強のために『プログラミング言語Go』を購入した。

www.kinokuniya.co.jp

最近、とある野望を抱いて「Go言語で実装してみよう」と思い立ったが、さすがにGo言語を全く知らない状態で実装を進めても無理があることがすぐにわかった。 そのため、積読状態であった『プログラミング言語Go』を読むことにした。 ただ読んでも継続できないと思ったので、ここのブログにエントリを書きこ残すことにする。

今回は1.1から1.3まで。

1.1 ハロー、ワールド

伝統のHello, World。

// helloworld.go
package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, 世界")
}
  • 実行するにはgo run helloworld.goを実行。ただし、今回はJetBrainsのGoglandを使っているのでCtrl+F10を押すか緑の矢印を押すだけ。
  • 各ソースファイルはpackage宣言から始まる。続いてインポートするパッケージを並べる。この順序は必ず守る必要がある。さもなければexpected 'package', found 'import'となる。
  • fmtはフォーマットされた入出力を扱う函数があるパッケージ。
  • mainパッケージは特別で、単独で実行可能なプログラムを定義する。
  • 函数宣言は予約語func、関数名、パラメータリスト(今回は空)、結果のリスト(今回は空)、本体からなる。
  • 同じ行2つ以上の文や宣言が現れない限り、文や宣言の最後にセミコロンを必要としない。「事実上、ある種のトークンの後の改行はセミコロンへ変換されます。」とある。
  • Goのコードを解析するためには改行する場所が問題となる。gofmtツールでコードを標準書式へ書き換える。

1.2 コマンドライン引数

// echo1.go
package main

import (
    "fmt"
    "os"
)

func main() {
    var s, sep string
    for i := 1; i < len(os.Args); i++ {
        s += sep + os.Args[i]
        sep = " "
    }
    fmt.Println(s)
}
  • osパッケージはプラットフォームとは独立した形式でOSを扱うためのパッケージ。Pythonosと目的は同じと思われる。
  • コマンドライン引数はos.Argsで扱う。os.Argsは文字列のスライスで動的に大きさが決まる配列要素のシーケンス。Pythonではsys.argsで配列であった。
  • os.Args[0]でコマンド自身の名前。os.Args[1:len(os.Args)]`が引数。インデックスの指定は半開。
  • Goglandのフォーマットと、gofmtは微妙に動作が異なる。Goglandの設定を変更すれば保存時にgofmtgoimportsが実行される。
  • varで変数宣言。変数が明示的に初期化されない場合は暗黙的に変数の型に対するゼロ値に初期化される。
  • forループはGoで唯一のループ文。C言語風の文、範囲を指定するものがある。
// echo2.go
package main

import (
    "fmt"
    "os"
)

func main() {
    s, sep := "", ""
    for _, arg := range os.Args[1:] {
        s += sep + arg
        sep = " "
    }
    fmt.Println(s)
}

効率の良い方法はstringsJoinを使うことである。

//echo3.go
package main

import (
    "fmt"
    "os"
    "strings"
)

func main() {
    fmt.Println(strings.Join(os.Args[1:], " "))
}

1.3 重複した行を見つける

// dup1.go
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    input := bufio.NewScanner(os.Stdin)
    for input.Scan() {
        counts[input.Text()]++
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }

}
  • if の条件部分は丸括弧で囲まない。本体は波括弧で囲む。
  • make(map[string]int)で文字列がキーで整数値が値である空のマップを作成する。makeにはほかの用途もある(がまだ書かれていない)。
  • マップの繰り返しの順序は規定されていない。
  • input := bufio.NewScanner(os.Stdin)で標準入力から読み込むスキャナができる。input.Scan()を呼び出すごとに1行読み込み改行文字を取り除く。中身はinput.Text()で取り出す。
  • fmt.PrintfC言語みたいなフォーマットされた文字列を出力する。
// dup2.go
package main

import (
    "bufio"
    "fmt"
    "os"
)

func main() {
    counts := make(map[string]int)
    files := os.Args[1:]
    if len(files) == 0 {
        countLines(os.Stdin, counts)
    } else {
        for _, arg := range files {
            f, err := os.Open(arg)
            if err != nil {
                fmt.Fprintf(os.Stderr, "dup2: %v\n", err)
                continue
            }
            countLines(f, counts)
            f.Close()
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}

func countLines(f *os.File, counts map[string]int) {
    input := bufio.NewScanner(f)
    for input.Scan() {
        counts[input.Text()]++
    }
}
  • os.Openはファイル*os.Fileとエラーの結果を返す。nilと一致するかどうかで成功の可否を判定する。C言語っぽい。
  • このファイル処理はストリーム処理となる。
// dup3.go
package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "strings"
)

func main() {
    counts := make(map[string]int)
    for _, filename := range os.Args[1:] {
        data, err := ioutil.ReadFile(filename)
        if err != nil {
            fmt.Fprint(os.Stderr, "dup3: %v\n", err)
            continue
        }
        for _, line := range strings.Split(string(data), "\n") {
            counts[line]++
        }
    }
    for line, n := range counts {
        if n > 1 {
            fmt.Printf("%d\t%s\n", n, line)
        }
    }
}
  • ioutil.ReadFileはファイル全体を一気にメモリへ読み込む函数。楽であるが、ファイルが大きいと辛そう。

感想

  • まだ最初の数ページしか読めていない段階であるが、標準ライブラリが充実したC言語という印象。当然この印象は今後覆されるはずである。
  • ファイルの読み書きがPythonと比べて面倒である。Javaでも面倒だなと思ったので、型を考慮したファイルの読み書きはどうしてもこうなってしまうのかもしれない。
  • PyCharmと比べてGoglandは完成度がそれほど、という印象。Early Access Programsに文句を言うほうがおかしいけど。