Go は Google のオープンソースプログラミング言語で、シンプルで信頼性が高く効率的なソフトウェアのプログラミングを簡単にします。 Go は、Tony Hoare の「 Communicating Sequential Processes 」で始まったプログラミング言語の一部であり、次のプログラミング言語も含まれています。

Go プロジェクトには現在 1,800 人を超える貢献者がおり、Google の Distinguished Engineer であるRob Pikeが主導しています。 Pike はもともとC++ のコンパイルにうんざりしていたため、 C++ の代替として Go を開発しました。 2012 年に Go が発表されてから何が一番驚いたか尋ねると、Pike 氏は次のように答えました。「C++ プログラマーが Go を代替手段として見るだろうと予想していました。その代わり、ほとんどの Go プログラマーは Python や Ruby などの言語を使用しています。」

当時の C++ および Java コミュニティからのほぼ満場一致の要求は、クラスとジェネリックスを Go に追加することでした。 Pike と他の人たちは、2022 年までの長い間、これと戦いました。Go バージョン 1.18 では、ジェネリックが導入されました。

この記事では、Go が他のプログラミング言語とどのように違うのかを見ていきます。もちろん、同時実行パターンと新しいジェネリックについても検討します。

スライス

Go は配列の概念を拡張して、可変サイズのスライスを含めます。スライスは値の配列を参照し、長さが含まれます。例: [ ]TTの要素を持つスライスです。以下のコードでは、符号なしバイトのスライスのスライスを使用して、作成した画像のピクセルを保存します。ピクセル値の範囲は 0 ~ 255 です。 Go プログラムはpackage mainで開始されます。 importステートメントは、C および C++ のincludeステートメントの拡張バージョンです。

package main

import "code.google.com/p/go-tour/pic"

func Pic(dx, dy int) [][]uint8 {

slice := make([][]uint8, dy)

for i := range slice {

slice[i] = make([]uint8, dx)

for j := range slice[i] {

slice[i][j] = uint8(i * j)

 Golang チュートリアル: Go でプログラミングする方法

}

}

return slice

}

func main() {

pic.Show(Pic)

}

:=構文は変数を宣言して初期化し、コンパイラーは可能な限り型を推測します。また、 makeスライスやその他のタイプの作成に使用されることにも注意してください。 for...rangeループは、C# のfor...inループと同等です。図 1 に示すパターンは、上記の内側ループの式(i*j)によって決定されます。詳細については、 pic パッケージとそのソース コードを参照してください。

 Golang チュートリアル: Go でプログラミングする方法

地図

Go のmapステートメントは、キーを値にマップします。 slice newではなくmake使用してマップを作成します。次の例では、文字列キーを整数値にマップします。このコードは、マップ要素の挿入、更新、削除、テストを示します。

package main

import "fmt"

func main() {

m := make(map[string]int)

m["Answer"] = 42

fmt.Println("The value:", m["Answer"])

m["Answer"] = 48

fmt.Println("The value:", m["Answer"])

delete(m, "Answer")

fmt.Println("The value:", m["Answer"])

v, ok := m["Answer"]

fmt.Println("The value:", v, "Present?", ok)

}

これはプログラムの印刷出力です。

 Golang チュートリアル: Go でプログラミングする方法

The value: 42

The value: 48

The value: 0

The value: 0 Present? false

構造と方法

Go にはクラスはなく、いわゆるstructがあります。これは、 fieldsと呼ばれる一連の名前付き要素です。各field nametypeあります。 methodは受信側の関数です。 method宣言は、識別子 (メソッド名) をメソッドにバインドし、それをレシーバーの基本型に関連付けます。

この例では、2 つの浮動小数点fields X と Y とメソッドAbsを含むVertex structを宣言します。大文字で始まるフィールドは公開されています。小文字で始まるフィールドはプライベートです。フィールドとメソッドはドット表記 ( . ) を使用してアドレス指定できますが、アンパサンド ( & ) は C のようにポインターを表します。このプログラムは印刷出力5を作成します。

package main

import (

"fmt"

"math"

)

type Vertex struct {

X, Y float64

}

func (v *Vertex) Abs() float64 {

return math.Sqrt(vX*vX + vY*vY)

}

func main() {

v := &Vertex{3, 4}

fmt.Println(v.Abs())

}

インターフェース

インターフェイス タイプはメソッド セットによって定義されます。値には、これらのメソッドを実装する任意の値を含めることができます。次の例では、インターフェイスAbserとタイプAbserの変数 ( a ) を定義します。

package main

 Golang チュートリアル: Go でプログラミングする方法

type Abser interface {

Abs() float64

}

func main() {

var a Abser

f: MyFloat(-math.Sqrt2

v = Vertex{3, 4}

a = f // a MyFloat implements Abser

a = &v // a *Vertex implements Abser

// In the following line, v is a Vertex (not *Vertex)

// and does NOT implement Abser.

a = v

fmt.Println(a.Abs())

}

type MyFloat float64

func (f MyFloat) Abs() float64 {

if f < 0 {

return float64(-f)

}

return float64(f)

}

type Vertex struct {

X, Y float64

 Golang チュートリアル: Go でプログラミングする方法

}

a=fおよびa=&v代入は機能しますが、 a=v代入はコンパイルすらできないことに注意してください。前のセクションで見たVertexAbsメソッドは、そのレシーバーとしてVertexタイプを指すポインターを持っています。 *Vertex Abserを実装しますが、 Vertex実装しません。

スイッチ

Go のswitchステートメントは、他の C 系プログラミング言語の switch ステートメントに似ていますが、 case単純な値だけでなく、型や式も使用できる点が異なります。ケースは、 fallthroughステートメントで終了しない限り、自動的に中止されます。ケースは定義された順序で評価されます。

package main

import (

"fmt"

"runtime"

)

func main() {

fmt.Print("Go runs on ")

switch os = runtime.GOOS; os {

case "darwin":

fmt.Println("macOS.")

case "linux":

fmt.Println("Linux.")

default:

// freebsd, openbsd,

// plan9, windows...

fmt.Printf("%s.", os)

}

}

ゴルーチン

ゴルーチンは、Tony Hoare の「Communicating Sequential Processes」の精神に基づいた、本質的に非常に軽量なスレッドです。次の例では、 func mainの最初の行はsay関数を非同期的に呼び出し、2 行目はそれを同期的に呼び出します。違いは、非同期ゴルーチンのgo修飾子の使用にあります。

package main

 Golang チュートリアル: Go でプログラミングする方法

import (

"fmt"

"time"

)

func say(s string) {

for i := 0; i < 5; i++ {

time.Sleep(100 * time.Millisecond)

fmt.Println(s)

}

}

func main() {

go say("world")

say("hello")

}

ゴルーチン、チャネル、およびselectステートメントは、Go の拡張性の高い同時実行性の中核を形成し、Go 言語の最も強力なセールス ポイントの 1 つです。さらに、Go には従来の同期オブジェクトもありますが、必要になることはほとんどありません。このプログラムは以下を出力します。

hello

world

hello

world

hello

world

hello

world

 Golang チュートリアル: Go でプログラミングする方法

hello

チャンネル

Go のチャネルは、並行して実行される関数が通信できるようにするメカニズムを提供します。これを行うために、特定の要素タイプの値を送受信します。以下に例を示します。

package main

import "fmt"

func sum(s []int, c chan int) {

sum := 0

for _, v := range s {

sum += v

}

c <- sum // send sum to c

}

func main() {

s := []int{7, 2, 8, -9, 4, 0}

c := make(chan int)

go sum(s[:len(s)/2], c)

go sum(s[len(s)/2:], c)

x, y := <-c, <-c // receive from c

fmt.Println(x, y, x+y)

}

初期化されていないチャネルの値はゼロであることに注意してください。 c = make(chan int)整数の双方向チャネルを作成します。一方向の送信 ( <-c ) チャネルと受信 ( c<- ) チャネルを作成することもできます。次に、 aの前半と後半のスライスを使用してsum非同期に呼び出します。次に、整数変数xyチャネルから 2 つの合計を受け取ります。式「 for _, v range a 」では、アンダースコア ( _ ) により、 for...rangeループからの最初の結果値 (インデックス) が無視されます。プログラムの出力は 17 -5 12 です。

範囲と近接

次の例は、送信者がチャネルをcloseそれ以上値が送信されないことを示す方法を示しています。受信者は、受信した式に 2 番目のパラメーターを割り当てることで、チャネルが閉じられているかどうかを確認できます。

package main

import (

 Golang チュートリアル: Go でプログラミングする方法

"fmt"

)

func fibonacci(n int, c chan int) {

x, y := 0, 1

for i := 0; i < n; i++ {

c <- x

x, y = y, x+y

}

close(c)

}

func main() {

c := make(chan int, 10)

go fibonacci(cap(c), c)

for i := range c {

fmt.Println(i)

}

}

mainの 3 行目のforループ ( for i := range c ) は、チャネルが閉じるまで繰り返しチャネルから値を受け取ります。チャネルのcapは容量、つまりチャネル内のバッファのサイズです。これは、 mainの最初の行のように、チャネルを作成するときにオプションの 2 番目の引数として設定されます。 fibonacci関数の代入ステートメントのコンパクトな形式に注目してください。プログラムの出力は、フィボナッチ数列の最初の 10 個の値 (0 ~ 34) です。

選択する

selectステートメントは、可能なsend receiveの範囲から実行する操作を選択します。 switchステートメントと類似点がありますが、すべてのケースは通信プロセスを指します。 selectいずれかのケースが実行可能になるまでブロックし、その後、それを実行します。複数のケースが用意されている場合は、ランダム原理が使用されます。

package main

import "fmt"

func fibonacci(c, quit chan int) {

x, y := 0, 1

 Golang チュートリアル: Go でプログラミングする方法

for {

select {

case c <- x:

x, y = y, x+y

case <-quit:

fmt.Println("quit")

return

}

}

}

func main() {

c := make(chan int)

quit := make(chan int)

go func() {

for i := 0; i < 10; i++ {

fmt.Println(<-c)

}

quit <- 0

}()

fibonacci(c, quit)

}

ここで、 main関数は 2 つのバッファされていないチャネル (結果用とquit信号用) を使用してfibonacci関数を呼び出します。 fibonacci関数は、 selectステートメントを使用して両方のチャネルを待機します。 main の 3 行目で始まる匿名の非同期go関数は、値 ( <-c ) の受信を待機してから値を出力します。 10 個の値の後、 fibonacci関数が停止するようにquitチャネルが設定されます。

同時実行パターン

Go プログラミング言語のユニークな機能をいくつか見てきました。次に、プログラミング例でこれらがどのように連携するかを見てみましょう。まずは Go のいくつかの同時実行パターンから始めます。どちらも 2012 年の Rob Pike の講義から引用したものです。

同時実行パターン #1: ファンイン

この例では、 select使用して、2 つの文字列入力チャネルinput1input2 1 つのバッファされていない出力チャネルcに結合するファンイン goroutine を作成します。 selectステートメントを使用すると、 fanIn両方の入力チャンネルを同時にリッスンし、準備ができた入力チャンネルを出力チャンネルに転送できます。どちらの場合も、それぞれの入力チャネルからの文字列を格納するために同じ一時変数名が使用されることは問題ではありません。

package main

func fanIn(input1, input2 <-chan string) <-chan string {

c := make(chan string)

go func() {

for {

select {

case s := <-input1: <- s

case s := <-input2: c <- s

}

}

}()

return c

}

同時実行パターン #2: 並列検索

次の同時実行の例では、Google 検索エンジンと同様に、インターネット上で並列検索を実装します。 replicas ...Search関数の可変パラメータですが、 SearchResultどちらも別の場所で定義された型です。

package main

func First(query string, replicas ...Search) Result {

c := make(chan Result)

searchReplica := func(i int) { c <- replicas[i](query) }

for i := range replicas {

go searchReplica(i)

}

return <-c

}

呼び出し元は、指定された数 (N) の検索サーバー関数をFirst関数に渡します。これにより、結果用のチャネルcが作成され、 i番目のサーバーにクエリを実行する関数が定義され、それがsearchReplicaに保存されます。 First 、N 個のサーバーすべてに対して非同期にsearchReplica呼び出し、常にチャネルcで応答を返します。

Go のパッケージ

それでは、いくつかのパッケージを見てみましょう。

http パッケージ

net/http Go パッケージは、HTTP クライアントとサーバーの実装を提供します。この例では、 /usr/share/docディレクトリの内容を Web クライアントに返す単純な Web サーバーを実装します。

package main

import (

"log"

"net/http"

)

func main() {

// Simple static webserver:

log.Fatal(http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc"))))

}

この例は、Go Playground オンライン環境では正しく動作しません。ただし、Mac のコマンド ラインで実行すると、http://localhost:8080/ を要求する次の内容が Web ブラウザに返されます。

  • バッシュ/

  • ccid/

  • カップ/

  • グロフ/

  • ntp/

  • 接尾辞/

テンプレートパッケージ

html/templateパッケージは、コード インジェクションから保護された HTML 出力を生成するデータ駆動型のテンプレートを実装します。例えば:

package main

import "html/template"

func main() {

t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)

err = t.ExecuteTemplate(out, "T", "<script>alert('you have been pwned')</script>")

}

上記のコードは HTML 出力を生成します。

Hello, &lt;script&gt; alert(&#39; you have been pwned&#39;)&lt;/script&gt;!

この例でhtml/templateパッケージによって追加されたエスケープがなければ、次の実行可能な JavaScript 文字列が生成された可能性があります。

Hello, <script> alert(' you have been pwned')</script>!

Go のジェネリックス

前述したように、ジェネリックは Go に対して長年要望されてきた機能です。現在、それらはコーディング言語に深く統合されており、型制限の概念が含まれています。これは、関数の型パラメータが角括弧内で関数の引数の前に配置されることを意味します。一般に、ジェネリック型で実装された関数は、 any型で実装された関数よりも効率的です。コード内に引数の型だけが異なる関数のグループがある場合は、代わりに汎用バージョンを作成することを検討してください。次の 2 つの例は、「 A Tour of Go 」からのものです。

ジェネリックスの例 #1: 比較可能な型のインデックス関数

次のコードではfunc Index[T comparable](s []T, x T) Tの型パラメーターを持ち、 s組み込みの制約comparable満たす任意の型Tのスライスであることを示します。 xも同じ型の値であることに注意してください。

package main

import "fmt"

// Index returns the index of x in s, or -1 if not found.

func Index[T comparable](s []T, x T) int {

for i, v := range s {

// v and x are type T, which has the comparable

// constraint, so we can use == here.

if v == x {

return i

}

}

return -1

}

func main() {

// Index works on a slice of ints

si := []int{10, 20, 15, -10}

fmt.Println(Index(si, 15))

// Index also works on a slice of strings

ss := []string{"foo", "bar", "baz"}

fmt.Println(Index(ss, "hello"))

}

整数と文字列はどちらも同等であるため、 Index関数は両方を受け入れます。この例では、正解である 2 と -1 が出力されます。 Go は、C や C++ と同様に 0 からカウントを開始することに注意してください。

ジェネリックの例 #2: ジェネリック型

この例では、任意の値の型を含むリンク リストの単純な型宣言を示します。演習として、次のリストの実装に機能を追加してください。

package main

// List represents a singly-linked list that holds

// values of any type.

type List[T any] struct {

next *List[T]

val T

}

func main() {

}

追加する機能の多さや少なさはあなた次第です。少なくとも要素にはNext()実装し、リストにはNew()Front()Back() 、およびInsertAfter()実装することをお勧めします。基本的にBack()InsertAfter()を実行するAdd()メソッドも適しています。Length Length()Index()Remove() 、そしておそらくInsertBefore()メソッドも同様です。

Go によるプログラミング: 追加リソース

Go について詳しくは、次のリンクをご覧ください。

  • Go プログラミング言語の公式ウェブサイト

  • Go ドキュメント

  • 「囲碁ツアー」

  • Goの仕様

  • Go パッケージの概要

  • 「効果的な碁」

  • Go の同時実行パターン

  • ジェネリック医薬品の紹介

  • 型パラメータの提案

  • 制約パッケージ

(FM)

この投稿は、米国の姉妹誌 InfoWorld の記事に基づいています。