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 では、ジェネリックが導入されました。
#JavaScriptと$PHPに関するミームはこれで十分です…
#Golang 、 #Python 、 #Cミームの時間です😏 pic.twitter.com/tqvQCLKKX5— サイバーパンダ🐼 (@realcyberpanda) 2021年2月21日
この記事では、Go が他のプログラミング言語とどのように違うのかを見ていきます。もちろん、同時実行パターンと新しいジェネリックについても検討します。
スライス
Go は配列の概念を拡張して、可変サイズのスライスを含めます。スライスは値の配列を参照し、長さが含まれます。例: [ ]T
型T
の要素を持つスライスです。以下のコードでは、符号なしバイトのスライスのスライスを使用して、作成した画像のピクセルを保存します。ピクセル値の範囲は 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)
}
}
return slice
}
func main() {
pic.Show(Pic)
}
:=
構文は変数を宣言して初期化し、コンパイラーは可能な限り型を推測します。また、 make
スライスやその他のタイプの作成に使用されることにも注意してください。 for...range
ループは、C# のfor...in
ループと同等です。図 1 に示すパターンは、上記の内側ループの式(i*j)
によって決定されます。詳細については、 pic パッケージとそのソース コードを参照してください。
地図
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)
}
これはプログラムの印刷出力です。
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
構造と方法
Go にはクラスはなく、いわゆるstruct
があります。これは、 fields
と呼ばれる一連の名前付き要素です。各field
name
とtype
あります。 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
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
}
a=f
およびa=&v
代入は機能しますが、 a=v
代入はコンパイルすらできないことに注意してください。前のセクションで見たVertex
のAbs
メソッドは、そのレシーバーとして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
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
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
非同期に呼び出します。次に、整数変数x
とy
チャネルから 2 つの合計を受け取ります。式「 for _, v range a
」では、アンダースコア ( _
) により、 for...range
ループからの最初の結果値 (インデックス) が無視されます。プログラムの出力は 17 -5 12 です。
範囲と近接
次の例は、送信者がチャネルをclose
それ以上値が送信されないことを示す方法を示しています。受信者は、受信した式に 2 番目のパラメーターを割り当てることで、チャネルが閉じられているかどうかを確認できます。
package main
import (
"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
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 つの文字列入力チャネルinput1
とinput2
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
関数の可変パラメータですが、 Search
とResult
どちらも別の場所で定義された型です。
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, <script> alert(' you have been pwned')</script>!
この例で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 の記事に基づいています。