まえがき
風邪をひいて会社を休んだので、この機会に前から気になっていたGoを勉強しようと思い、以下のサイトで勉強を始めた。
https://golang.org/doc/
学んでいくうちに、これは他の言語にはなかった機能だなと思ったものを書いていく。
「他の言語にはなかった機能」とは、私にとって初めて目にする機能である。以下のプログラミング言語はだいたい分かるはずなので、これらの言語には無い機能なはず。
Pascal, C, Java, Perl, Python, TypeScript
他の言語には同じような機能があるのかもしれないが、容赦して欲しい。
スライスのcapacity
配列は、C言語と同じく固定長である。以下では、長さ6のint配列を定義をする。
arrayedPrimes := [6]int{2, 3, 5, 7, 11, 13}
配列は固定長で使い勝手が悪いので、以下のようにスライスが多用される。
slicedPrimes := []int{2, 3, 5, 7, 11, 13}
スライスにはlengthとcapacityという概念があり、lengthはスライスが参照する配列のサイズ、capacityはスライスが参照する配列の終端までのサイズを表す。
ゆえに、以下のような挙動になる。
firstSlice := slicedPrimes[0:4] // [2, 3, 5, 7]
len(firstSlice) // -> [2, 3, 5, 7]なので4
cap(firstSlice) // -> [2, 3, 5, 7, 11, 13]なので6
secondSlice := slicedPrimes[2:4] // [5, 7]
len(secondSlice) // -> [5, 7]なので2
cap(secondSlice) // -> [5, 7, 11, 13]なので4
interfaceの自動実装
goでは、クラスの継承は無い。interfaceは存在するが、interfaceをプログラマーが実装するということも無い。
代わりに、ある構造体の持つメソッドがどこかで定義されたinterfaceのメソッドをすべて実装している場合、その構造体がinterfaceを実装していると勝手にみなされる。
例えば、以下の構造体に、
type person struct {
firstname string
sirname string
age int
}
以下のメソッドが定義されているとする。
func (p person) getSirName() string {
return p.sirname
}
func (p person) getFirstName() string {
return p.firstname
}
func (p person) getAge() int {
return p.age
}
それとは別に、以下の2つのインターフェースが定義されているとする。
type japaneseFriend interface {
getSirName() string
getAge() int
}
type americanFriend interface {
getFirstName() string
getAge() int
}
この場合、構造体personではimplement文などは書いていないが、勝手にjapaneseFriendおよびamericanFriendの双方を実装していることになる。
以下のコードは、personをamericanFriendインターフェース型の変数に代入してるが、switch文で型チェックをするとjapaneseFriend側のコードが実行される。personはjapaneseFriendでもありamericanFriendでもあるということが分かる。
// americanFriend型の変数に代入
var friend americanFriend = person{"tanaka", 20}
switch friend.(type) {
case japaneseFriend:
// japaneseFriend型でもあるので、こちらのコードが実行される
fmt.Println("Japanese Friend")
case americanFriend:
fmt.Println("American Friend")
default:
fmt.Println("none of above")
}
例外ではなくError interface
goには例外が無い。なので、メソッド実行のエラーを検出するには、メソッド実行時の戻り値であるerrorの値のチェックを行う。
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
return
}
fmt.Println("Converted integer:", i)
errorの値がnilであれば問題なし、nilでなければエラーが発生している。
goroutine
goで非同期処理を行うには、メソッド呼び出しの前にgoと書けば良い。
func someMethod() {
fmt.Println("hello")
fmt.Println("goroutine")
}
func main() {
go someMethod() // 直ちに処理が終了するのでprintはされない
}
goroutine間での値の受け渡しにはchannelを使う。channelはFIFOなのでMessageQueueだと思えば良いだろう。
ch := make(chan string)
でstring型のchannelを生成し、非同期処理を行うメソッドに渡す。
非同期処理側では、ch <- "hello"
のようにして文字列をchannelに入れる。
chへの書き込みを待つ側では、for msg := range ch
のようにして、chにメッセージが書き込まれるまでスレッドの処理をブロックし、書き込まれたらmsgに文字を代入する。
どこかでclose(ch)
が呼ばれたらforを抜ける。
func someMethod(ch chan string) {
ch <- "hello"
ch <- "goroutine"
close(ch)
}
func main() {
ch := make(chan string)
go someMethod(ch)
for msg := range ch {
fmt.Println(msg)
}
}
A Tour of GoのExercise: Web Crawlerの自分の解法
ちょっと大変だったので誰かの役に立つかと思って書いておく。
package main
import (
"fmt"
"sync"
)
type safeSiteMap struct {
crawledMap map[string]string
mutex sync.RWMutex
}
func (siteMap *safeSiteMap) put(url string, body string) {
siteMap.mutex.Lock()
siteMap.crawledMap[url] = body
siteMap.mutex.Unlock()
}
func (siteMap *safeSiteMap) hasCrawled(url string) bool {
siteMap.mutex.RLock()
_, ok := siteMap.crawledMap[url]
siteMap.mutex.RUnlock()
return ok
}
type Fetcher interface {
// Fetch returns the body of URL and
// a slice of URLs found on that page.
Fetch(url string) (body string, urls []string, err error)
}
// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher, siteMap safeSiteMap, wg *sync.WaitGroup) {
defer wg.Done()
var concreteCrawl func(string, int)
concreteCrawl = func(url string, depth int) {
// ensure wg.Done() when method returns
defer wg.Done()
if depth <= 0 {
return
}
body, urls, err := fetcher.Fetch(url)
if err != nil {
fmt.Println(err)
return
}
siteMap.put(url, body)
fmt.Printf("found: %s %q\n", url, body)
for _, u := range urls {
if siteMap.hasCrawled(u) {
continue
}
wg.Add(1)
go concreteCrawl(u, depth-1)
}
}
wg.Add(1)
go concreteCrawl(url, depth)
return
}
func main() {
siteMap := safeSiteMap{crawledMap: make(map[string]string)}
var wg sync.WaitGroup
wg.Add(1)
go Crawl("https://golang.org/", 4, fetcher, siteMap, &wg)
wg.Wait()
for url, body := range siteMap.crawledMap {
fmt.Println(" " + url + ": " + body)
}
}
// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult
type fakeResult struct {
body string
urls []string
}
func (f fakeFetcher) Fetch(url string) (string, []string, error) {
if res, ok := f[url]; ok {
return res.body, res.urls, nil
}
return "", nil, fmt.Errorf("not found: %s", url)
}
// fetcher is a populated fakeFetcher.
var fetcher = fakeFetcher{
"https://golang.org/": &fakeResult{
"The Go Programming Language",
[]string{
"https://golang.org/pkg/",
"https://golang.org/cmd/",
},
},
"https://golang.org/pkg/": &fakeResult{
"Packages",
[]string{
"https://golang.org/",
"https://golang.org/cmd/",
"https://golang.org/pkg/fmt/",
"https://golang.org/pkg/os/",
},
},
"https://golang.org/pkg/fmt/": &fakeResult{
"Package fmt",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
"https://golang.org/pkg/os/": &fakeResult{
"Package os",
[]string{
"https://golang.org/",
"https://golang.org/pkg/",
},
},
}
感触
斬新な機能を盛り込みながらも、実行速度を上げるためなのかやはりC言語っぽい匂いも感じる言語。
5時間くらいの学習で可能性は感じられたが、エコシステムがどうなっているのかは分からないので実際の開発でどれくらい楽になるのかは分からず。
とはいえ、C++と比較して不具合は出にくそうだと思うので、今後様々なプロジェクトがGoになったら幸せなのだろう。偉い人頑張れ。組み込みとかもっと頑張れ。