ゼロから学ぶGo言語プログラミング(7) A Tour of Go 17~24

f:id:belbo:20140616112930j:plain

A Tour of Goの続き。 制御構造からです。

17.For

  • Golangの繰り返しはwhileなしのforのみ
  • 初期化や条件に()なし
  • {}は必須

18.For continued

for ; sum < 1000; {

セミコロンありで処理なしってのは分かりにくいような。

19.For is Go's "while"

ということで、

for sum < 1000 {

とすればwhile相当に。

20.Forever

空条件でシンプルに無限ループ。

抜けるのはbreakとか?

21.If

ifもfor同様、()なしで{}必須。 自分の好みに合ってて良い。

しかし、returnがバカスカ出てくるイメージだなあ。

22.If with a short statement

Goのifは初期化が可能であり、初期化と条件は;で区切る。

初期化での宣言はそのifのみで有効。 つまりifはスコープを持つ。

23.If and else

スコープがelseでも共通ということは、確かに色んな処理をifで分かりやすく書きやすそう。

24.Exercise: Loops and Functions

課題はニュートン法を実装しなさい、というもの。 でもこの手の数学がろくに身についていない。

取り敢えず、提示してくれている数式をベタにコードにするところから始めて、自分で精度を指定したものと、math.Sqrt()の結果と一致するまでのものとを作り分けたり、複数の戻り値を使って回数も返すようにしたりした結果、以下のコードになった。

package main

import (
    "fmt"
    "math"
)

func SqrtA(x float64) (float64, int) {
    const a = 0.00000000000000001
    z := float64(1)
    i := 1
    for t := float64(0); math.Abs(t-z) > a; i++ {
        t = z
        z = z - (((z * z) - x) / (x * x))
    }
    return z, i
}

func SqrtB(x float64) (float64, int) {
    z := float64(1)
    m := math.Sqrt(x)
    i := 1
    for ; m != z; i++ {
        z = z - (((z * z) - x) / (x * x))
    }
    return z, i
}

func main() {
    n := 6.0
    fmt.Println(math.Sqrt(n))
    fmt.Println(SqrtA(n))
    fmt.Println(SqrtB(n))
}

ひとまず目的の算出はできているけれど、肝心の算出部分がひどく不格好な式になってる。 golangもだけれど、数学とコンピューティングの基礎を積み上げないと難しそう。

しかし、やはり課題をひとつ解いてみるだけで、もやっとしていた概念の理解が明確になる。


制御構造は一旦終わりで、続きは型周りらしい。

ゼロから学ぶGo言語プログラミング(6) A Tour of Go 1~17まで

実践的な言語ガイドを見ながら制御構造の確認をしようと思っていましたが、ちょっと心変わりして、公式チュートリアルの「A Tour of Go」をひと通り進めることにしました。

A Tour of Go

f:id:belbo:20140616112930j:plain

これは、ブラウザで動作するインタラクティブなGoの簡易的な実行環境に、チュートリアルを添えたものです。 実行環境はGo Playgroundとほぼ同じですが、シンタックスハイライトが使えるなど、フロントエンドで少し違いもあります。

1.Hello, 世界

ツアーの先頭は、やはりHello Worldです。 これについては以前しっかり確認しているので、コードはスルーします。

チュートリアル本文には、このツアー環境の構成やショートカットなどの説明。 構成は"基本コンセプト、メソッドとインターフェース、並行性"となっていて、ショートカットは以下の2つが用意されています。

  • Shift + Enter : コードの実行
  • PageUp / PageDown : ページの切り替え

2.Go local

ツアーは多言語に対応していて、英語や日本語以外にも、スペイン語やフランス語、中国語や韓国語などでも進められます。 面白いのは、ヘブライ語カタルーニャ語ルーマニア語など、利用者のそう多くない言語がサポートされている点。 ロシア語などもっと話者の多いであろう言語がないので、有志が翻訳した言語から対応していっているのかも知れません。

3.The Go Playground

ツアーに使用するPlaygroundの説明。

  • ネットワークやファイルシステムへのアクセスができない
  • システム時刻は常に固定
  • シングルスレッドでしか動かない

4.Packages

パッケージのインポートについて、基本の説明。

気になったのはplayground環境ではmath/randがきちんと擬似乱数を返さず、結果は常に同じ値になる、という点。 乱数のシードを与えれば大丈夫、と書いてあるので、システム時刻が固定なのが理由でしょうか。

5.Imports

packageのimportは、複数まとめて書ける。 ということで、以下の3つは等価。

import "fmt"
import "math"
import (
    "fmt"
    "math"
)
import (
    "fmt"
)
import (
    "math"
)

importを書く場所は、先頭しかだめなんだろうか。

6.Exported names

Goのpackageは、大文字で指定した要素をエクスポート、という名前ベースの参照制御。

サンプルで配置されているコードは、math.piと小文字のpiを指定しているため、エラー。 この手のpackageに指定した要素がエクスポートされていない場合には、"cannot refer to unexported name ***"というエラーが検出されるようだ。

7.Functions

  • 関数宣言はfunc
  • 引数指定は、引数名の後ろに型を指定
  • 引数の括弧の後に戻り値の型を指定

なぜ型指定が後に来るのか、今は深追いせずにいよう。

8.Functions continued

型指定の省略記法。

func foo(a, b, c, d, e int, x, y, z string) int {
}

という風に、まとめて指定したければ、引数の順は型でまとまっている必要はある。

9.Multiple results

おお、ようやくお目にかかれた、複数の戻り値の返し方。

func foo(x, y int) (int, int) {
      return x+y, x*y
}

という風に、関数宣言の引数指定後に置く戻り値の型指定を、括弧に目的の個数の型を並べるようにし、returnで複数の値を渡す、ということだった。

もうひとつついでに気になったのは、

a, b := swap("hello", "world")

というコードの":="という記号。 代入のようだけど、多分この先出てくるだろう。

10.Named results

関数では戻り値に型だけではなく、予め名前を指定できるらしい。

そして名前を指定した戻り値が宣言されている場合、returnで返したい値をわざわざ指定しなくても済む。

しかし以下のように、戻り値名を指定しつつ、異なる値を返すこともできるようだ。

func split(sum int) (xx, y int) {
    xx = sum * 4 / 9
    y = sum - xx
    return sum, sum
}

これはちょっと混乱しそう。

11.Variables

やっと出てきた変数宣言。

JavaScriptなんかで見慣れた、var。 引数や戻り値の宣言と同じく、同一の型であれば、名前を複数ならべて、後ろに一括で型を書ける。

12.Variables with initializers

varによる変数の宣言時には、変数の初期化が出来る。 更にその初期化時には、型が自動判定される。

これ、浮動小点数の精度とか、リテラルで初期化しつつ、型も指定したい場合どうするんだろう。 変数側に型を付けるのかな。

13.Short variable declarations

関数内だけで使える、暗黙的型宣言を行う代入記法":="。 さっき出てきたのはこれでした。

この記法が関数内だけに限定される理由はなんだろう。 関数外ってことは、packageとしてエクスポートされ得る対象だとかいうことに関係があるんだろうか。

14.Basic types

やっと出会えた、標準の型一覧。

bool, string, intあたりはごく当たり前だけど、符号付きで整数を放り込むとoverflowだよと警告されるuintがある。 intとuintには、サイズなしのものと、8,16,32,64のサイズ付きがある。 サイズなしのものは、処理系で決まるのかな。 それとも、int32あたりの略記なのか。

floatは32か64か。 精度表記なしの"float"は無し。

byteがuint8のエイリアスとして定義されている。

見慣れないのは、runeとcomplex。

runeは、どうやらUnicodeの文字ひとつを表す型らしい。 chrとかruneと呼ぶのは、Go特有なんだろうか。 よくあるchrとかではUnicode文字という扱いが曖昧だから、新しい名前を付けたのか。

complexはなんなんだろう。 名前からして、複合的なデータを持つんだろうけど、謎。

そういえば、var宣言もimport同様、

var(
    foo int
    bar uint16
)

みたいにまとめ書きが出来たらしい。

そして、「初期化しつつ、型も自前で指定したい場合」は、やはり変数側に型を付けるで正解だった。

15.Constants

定数宣言はキーワードconstで。 これは関数内でも外でも同じ。

謎は、次の一文。

定数は、character、string、boolean、数値(numeric)のみで使えます。

character、string、boolean、数値(numeric)ってのは、組み込みの型のどれにどう対応しているのか、全然伝わってこない…。 そもそもconstには型が不要なのか??

16.Numeric Constants

さあ、これはよく理解できない。

数値定数は高精度な 値 ( values )です。型のない定数は、その状況によって必要な型を取ることになります。

constで宣言した定数は、それが数値であれば、「高精度な値」として扱われ、型は使用時に選択されるらしい。 定数は、ひとつの宣言を様々な状況で利用しうるから、変数での型とは異なる扱いなんだろうか。


取り敢えずここまで。

ゼロから学ぶGo言語プログラミング(5) 命名とセミコロン

引き続き、各種命名とセミコロンについて。

前回同様以下のドキュメントを参照していきます。

名前

パッケージ名の付け方

冒頭、パッケージのエクスポートが名前で制御されていることへの言及が。

たとえば名前の頭文字が大文字かどうかで、パッケージの外からの可視性が決まります。

先頭大文字の要素はエクスポートされ、それ以外は不可視。 publicだとかinitだとかに頼らず、名前のみで可視性が決まる。 ここもシンプルですが、頭が慣れるまで少し時間がかかりそうです。

慣例では、パッケージ名は小文字でひとつの単語です。アンダースコアや大文字が混ざって(mixedCaps)はいけません。パッケージ使用者がその名前をタイプすることを考慮して、簡潔すぎるぐらいにしてください。名前が重複することを気にする必要はありません。パッケージ名は、単にインポート時にデフォルトとして使われる名前であり、ソースコード全体でユニークである必要はありません。万が一、パッケージのインポート時に重複が起きたときは、別なローカルな名前をつけることができます。

ここもかなり重要そうな。 自分でパッケージ名付ける場合は、以下が基本らしい。

  • 小文字のみ
  • なるだけシンプルに
  • 重複を気にするな
  • 衝突はインポート側で解決できる

そして以前疑問だった、インポート時のパッケージ名のエイリアスを"."とできる件にも言及が。

import . 表記を使うとアクセス時にパッケージ名が不要となりますが、これはテストや他の一般的でない状況で使うためのものなので必要でない限り避けるべきです。

テストコードでは、パッケージ名を省略してアクセスできるようなインポートの仕方が、便利な場合があるということでしょうか。

エクスポート対象の名前の付け方

パッケージを経由してアクセスするエクスポート対象の命名は、使用されるシーンを意識すべき、ということか。

パッケージ名を使うので、エクスポートされる名前は、同じ名称が繰り返されることを避けるためにパッケージ名を利用することがあります。たとえば、bufioパッケージ内の、バッファ付きリーダ型は、BufReaderではなくReaderと名づけられています。

複雑もしくは微妙なニュアンスを持つものに名前をつけるときは、すべての情報を名前で表現しようとするより、通常は役立つドキュメントコメントを書いたほうがよいでしょう。

説明的で厳密な名前を取るよりも、冗長さを減らして直感的に"パッケージ.要素"という構造が表現できるようにせよ、という思想のようです。

これまで他の言語でなんとなくプログラミングをしていて、こういう命名にはいつもグダグダと悩んでいました。 しかし言語のガイドでこうばっさり言われると、これで良いのだと割りきれて、気持ちが良い。 但し、例に上がっていた次のケースは悩む。

ring.Ring型の新しいインスタンスを作成する関数(Go言語におけるコンストラクタ)は通常、NewRingと名づけられますが、Ringがそのパッケージからエクスポートされている唯一の型で、かつパッケージ名がringであれば、単にNewとします。この関数はパッケージを利用する側からは、ring.Newとなります。

ring.Ring が唯一のエクスポート対象ならそれでいいけれど、ring.Bufferとか新しいエクスポート対象が増えた場合、Newの意味がRingと対応しなくなります。 こういう拡張をするなということなのか、それとも、ring.Bufferが増えた時点でリファクタリングすればいい、ということなのか。

ゲッター・セッター

Goには組み込みのゲッター・セッターは無く、必要なら作りなさい、と。 そして、以下のように規則を設ける。

foo.bar のゲッターは foo.Bar() foo.bar のセッターは foo.SetBar()

getとsetがペアで刷り込まれていると少し違和感ありますが、コードにしてみると確かに自然です。

bar := foo.Bar()
if bar != boo {
    foo.SetBar(boo)
}

でも、Goってオブジェクト指向なの?? 今のところドットシンタックスでアクセスするのはパッケージのメンバーなんだけど…。 基礎の言語仕様を確認せずに、実践から入っている弊害ですかね。

インターフェース

Goにインターフェースが存在することは分かったけれど、

Read、Write、Close、Flush、Stringといったメソッドは、決められた役割とシグネチャを持ちます。

この辺の特別なメソッド名の立場がよくわからない。 そしてそれがインターフェースの場合にのみ言及される理由も。

インターフェース周りは、じっくり確認する。

MixedCaps

Go全般で、複数語で構成される命名では、区切りのアンダースコアは使わず、Capsを使う。

pythonでアンダースコア多用しているので、ちょっと違和感。 まあ慣れですね。

セミコロン

lexer(字句解析プログラム)がソースを調べて、ある単純なルールに基づき自動的にセミコロンを付け加えるので、打ち込むコードにはセミコロンがほとんど不要です。

JavaScriptの自動セミコロン挿入っぽい。 それが混乱をもたらす可能性がある点も。

ただし一行を複数ステートメントに分割するにはセミコロンが必要なので、このようなコードを記述するときはセミコロンを挿入してください。

基本的に、単一行に複数ステートメントをまとめる書き方は避けるべきなんだろう。

if i < f()  // 不正
{           // 不正
    g()
}

このコードが不正と扱われるのは、Go特有か。 でも { は if の後ろについていて欲しいので、全く苦にならない。


次は制御構造。

ゼロから学ぶGo言語プログラミング(4) コードフォーマット, コメント

一旦まっさらな所からGoを学びなおしている記事、4回目はコードフォーマットやコメントについて。

今回からは、以下のドキュメントを参照していきます。

このドキュメント、言語仕様やチュートリアルを読んでからと案内されていますが、内容を見る限り、ある程度はじめに目を通した方が良さそうです。

はじめに

GoにはGoのルールがあるから、既存の言語のコードをそのまま変換しても、良いGoのコードとはならないよ、という話。 当たり前なんですが、最初にわざわざこう釘を刺すのは、やはり独特な思想の言語だからでしょうか。

気になったのは以下の部分。

C++またはJavaプログラムをGo言語へ直接変換しても、あまりうまくは行きません。JavaのプログラムはあくまでJavaで書かれており、Go言語で書かれてはいないからです。

GoはC++とかJavaとか、これまで似た用途に使われてきた言語に似ていないんじゃないか、という印象があります。 どの言語もきっちり修めていない者の勝手な印象ですが、どちらかというとCに近くて、そこにpythonのようなスクリプト言語を足したような。 だからC++Javaのモダン版として取り組んでも駄目だよ、という事を言いたいような気がします。

まあ、C++Java含めて、そんなに既存の言語に染まれていない者にとっては、あまり悩む必要はない話ではあります。

ソースコードフォーマット

ソースコードの書き方は、どうしても個人の好みや組織のルールに差が生まれやすい。 そこでGoでは、なんとはじめからフォーマット用のツールを環境に同梱している。

Go Playgroundにもgofmtは提供されていて、上部の「Format」ボタンでフォーマットしてくれます。

gofmtというこのフォーマットツールを使えば、関わるコードはどれも同じフォーマットに統一出来る。 もちろんツールがサポートしていない細かな差異は残るでしょうが、不毛な争いはぐっと減る。 この仕組、全ての言語が取り入れて良い気もします。

ただ、そこまで同じフォーマットにするのであれば、pythonみたいにもっとインデントや改行に依存した構文を採用しても良かったと思いますが、その気配は今のところありません。 言語を設計する上で、大きなデメリットがあるのかも知れません。

インデント

なんとタブ限定。 スペースは原則禁止です。

以前から自分用のコードはどの言語でもタブで統一してきたので、これは非常にありがたい。 Go Playgroundで試すと、スペースでインデントしていても、gofmtがタブに揃えます。

ブラウザの標準タブ文字幅が広い(スペース8つ分)のは気になりますが、playgroundであれば特に困らない。 問題はブラウザ上でGoの開発を行うような環境の場合で、タブ文字幅を指定する方法が無いと、拒否感強くなりそうです。

行の長さ

これも、特に制限しないということで、ありがたい。 一律に上限を定めるほうが、かえって分かりにくい場合もあります。

行があまりにも長くなったときは、改行してタブでインデントしてください。

とありますが、例えば引数名が長く多い場合など、改行してインデントすべきか、gofmtが面倒見てくれないケースも出てきそうです。 こういうケースの処理は、既存のライブラリを参照すれば良さそうです。

括弧

Go言語はあまり括弧を必要とはせず、制御機構(if, for, switch)の構文には括弧を使いません。

とりあえずGoにはif, for, switchがあることと、python同様括弧は不要、ということがわかりました。 pythonにはswitchなくて分かりにくく感じたこともあったので、これもありがたい。

演算子の部分は「見たとおりの」とありますが、ビットシフトが使い慣れずいまいちしっくりこない。 シフト演算子は、加算演算子より優先度が高いのが前提で、「見たとおりの」なんでしょうか。 これは後々、演算子の優先順位などを確認します。

コメント

コメントもシンプルです。 / / のブロックコメントと、// のラインコメントのみ。

コメントははじめからドキュメント用途を想定されているようです。 他の言語のxdocのように、Goにはgodocというドキュメント抽出用の仕組みがあり、この仕組みを意識したコメントを書くよう

godoc用のコメントも、非常にシンプル。 パッケージでも関数でも変数でも、宣言前に置いたコメントが、ドキュメントで使用されます。

次は引き続き、名前とセミコロン。

ゼロから学ぶGo言語プログラミング(3) Hello World

プログラミング言語Goをのんびり一から学ぶ続き。 appengineのVMにGoで書かれたDockerが採用されたりで、モチベーションさらに上がりました。

今回はようやくHello World

Go Playground

きっちりGoの開発環境を用意するのは後回しにして、まずはコードに触れていきたいので、ブラウザ上で手軽にGoが試せる「Go Playground」を利用します。

Go Playgroundは公式サイトの一部なので、最新のバージョンも追いかけてくれる筈。 当面はこの環境で言語仕様を確認したりします。

Hello World

Go Playgroundにアクセスすると、以下のHello World("World"でなく"playground"ですが)に当たるコードがデフォルトで入力されています。

package main

import "fmt"

func main() {
    fmt.Println("Hello, playground")
}

Go Playgroundでこのまま「Run」すれば、画面下部の出力先に"Hello, playground"と出力されます。 favicongopherなので、gopherくんに挨拶されてる気分になりますね。

まずはこのコードをきちんと理解できるようになってから、あれこれ試します。

余談ですが、はてなブログシンタックスハイライトがGoに対応していて嬉しかったです。

package

1行目には、

package main

とあります。 packageという名前からして、プログラミングに使用するライブラリなど、何らかの塊を指定しているのだと思います。 そしてmainは、そのパッケージの名前でしょう。

Goのパッケージという概念についてきちんと確認するには、公開されている言語の仕様書を読む必要があります。

しかし、不満足な英語でこの手の文書を読むのは苦痛で、とっかかりとして良くありません。 幸い株式会社 エイベルが日本語訳を公開してくれているので、それを利用します。

まず、パッケージの概要。

Go言語のプログラムは、複数のパッケージをひとつにリンクすることによって作られます。さらに各パッケージは、そのパッケージに所属する定数、型、変数、関数を宣言しているひとつ以上のソースファイルから構成されます。これらパッケージ内の要素は、同一パッケージ内であれば別ファイルからアクセスすることができます。また要素がエクスポートされていれば、他パッケージからアクセスすることができます。

それから基本中の基本らしき記述。

各ソースファイルはパッケージ節で始まり、そのファイルが所属するパッケージを定めます。

リンクというのは、コンパイルする言語では耳にしますが、スクリプト言語でのimportのようなものでしょうか。 ここだけ読んでもまだ分かりません。

"package foo"という記述は、「パッケージ節」という扱いのようです。 節や文といった言語的な概念によって、具体的にプログラミング上何が変わってくるのか、これも確認が必要そうです。

重要っぽいのは、変数や関数などが「そのパッケージに所属」となっている点。 つまりパッケージは、名前空間であり、pythonでいうmoduleのようなものでもあるようです。 要素のエクスポートとある辺りも、pythonっぽい。

pythonは単一のファイルで書けばmodule意識しない作りも可能でしたが、"各ソースファイルはパッケージ節で始まり"とあるので、Goではpackage必須なんでしょう。 ということは、ワンライナーで何か書くのには向かない?…というか、スクリプトじゃないからワンライナーは考慮しなくていいのか。

他に、「ひとつ以上のソース・ファイル」となっているので、複数のファイルで単一のパッケージを構成できるようです。 パッケージが基本的な単位で、pythonのmoduleのような扱いなら、手軽に複数ファイルをまたげるのは楽で良さそう。

更にこんな記述がありました。

パッケージの実装は、同じパッケージ名を共有するファイル群によって構成されます。実装上、同一パッケージ内のすべてのソースファイルが、同一ディレクトリ内に置かれている必要があります。

同一とは、単一の階層を指しているんでしょうか。 であれば、foo/にfooパッケージを構成する全ファイルが入るとして、サブディレクトリの扱いはどうなるんでしょうか。 サブディレクトリはサブパッケージ? サブパッケージがfoo.barみたいにネストすると、更にpythonっぽくて分かりやすいんですが。

同一ディレクトリに異なるパッケージのファイルを置いた場合も、いまいちこの仕様書だけではわかりません。

package についてわかったこと

とりあえず今の時点では、packageに関して以下を理解したものとして、先に進みます。

  • Goではpackageという単位でプログラムを作成する
  • ソースファイルはpackage節で始まる
  • package節は"package パッケージ名"と書く
  • packageは他のパッケージからリンクされる
  • その際リンクした側に何を公開するかは選べる
  • packageは複数のファイルにまたがれる
  • 同じパッケージのソースファイルは同一ディレクトリに置く

package についてわからなかったこと

一方、次の点はこれからおいおい調べます。

  • リンクとは? スクリプト言語のimportとの違いは?
  • package節のpackage名は文字列型ではない?
  • 名前空間とpackageの関係
  • packageは常に必須か
  • packageでのエクスポート指定方法
  • 同一ディレクトリに異なるパッケージのソースファイルは混在可能か
  • packageのネストは可能か

packageに限らず、言語仕様はEBNF - Wikipediaという記法のメタ言語で色々書かれているようですが、Hello Worldには必須でも無し、細かい文法が問題になった場合のみ確認します。

main

前段でpackageという基本的な単位があることは理解できました。 次に、Hello Worldでのpackage名らしき、"main"。

このmainパッケージについて単独ではっきり取り上げているリソースが見つからなかったので、プログラムの初期化と実行 - Goプログラミング言語仕様 - golang.jpの後半部分の言及を参考にします。

mainパッケージと呼ばれる、他からインポートされることがないパッケージをリンクすることでプログラムは完成します。このmainパッケージから全てのパッケージが直接・間接的にインポートされます。mainパッケージは、パッケージ名はmainであり、かつ引数と戻り値を持たないmain関数を宣言していなければなりません。

ということで、mainパッケージは、プログラムに必ず必要な、その名の通り主となるパッケージを表す予約語のようです。 そしてmainパッケージは、同じくmainという名の関数も宣言必須。

おぼろげな記憶の中のCとかにおけるmain()に似てますが、Goのmainはパッケージと関数の2段階で必要です。

main についてわかったこと

  • Goのプログラムにはmainパッケージが必須
  • mainパッケージにはmain関数が必須
  • main関数は引数も戻り値も持ってはいけない

main についてわからなかったこと

  • mainパッケージまたはmain関数を宣言しないと何が起こる?
  • mainを異なる文字で置き換えることは可能か
  • mainが必須で面倒な点は?

import

よやく3行目、ただし空白行をはさんで実質2行目です。

import "fmt"

引き続き、言語仕様の日本語訳を参照します。

インポート宣言は、ソースファイル内に含まれている宣言がインポートしたパッケージ(§プログラムの初期化と実行)が持つ機能に依存していることを表し、かつそのパッケージでエクスポートされている識別子へのアクセスを可能にします。インポート名はアクセスに使用する識別子(PackageName)であり、ImportPathにはインポートされるパッケージを指定します。

そのプログラムが"依存"している他のパッケージを宣言し、インポートした側でどのような表現でアクセスするかを既定する。 これがimportのようです。

importの対象はパッケージ名ではなく、パスを指定するようです。 package節ではパッケージ名を指定していたため文字列型ではなかったんですが、importはパスなので文字列型になるっぽい。 相対パスになるようですが、その起点だとかコンパイルのことだとか、この手のパスの解決は鬼門が多いので、きちんと調べて抑えたほうが良さそう。

いわゆる as も用意されていて、これが"インポート名"として3種類紹介されています。

import   "lib/math"         math.Sin
import M "lib/math"         M.Sin
import . "lib/math"         Sin

当然一番上が基本ですが、2番めはインポートしたいパッケージ名によって、簡略化した名称などを使うかもしれません。 3番目の表記は、インポート対象のパッケージ名が失われてしまい、名前の衝突なども起きそうで、現時点ではあまりメリットを感じられません。 恐らく特定の用途には欠かせない、あるいはかなり楽の出来るケースがあるのでしょう。

こうやってimportされたパッケージは、何をimportした側に公開するのか、何らかの方法で決めているようです。 そして例えばfooパッケージをimportした際、fooがbar()を公開していれば、インポートした側では foo.bar() 記述して別パッケージの要素にアクセスできる。 ドット区切りである点も含め、こういった挙動はpythonに近く、すんなり入っていきます。 しかし、パッケージ内のサブパッケージなんていう考え方はまだ出てきません。

import についてわかったこと

  • importは依存するパッケージの宣言
  • 依存していないなら書かない
  • importしたいパッケージはパスで指定する
  • import時にインポートした側で使用するパッケージ名を指定できる
  • importしたパッケージの要素にアクセスするには foo.bar() のようにドットを用いる

import についてわからなかったこと

  • importのパスはどういう規則で解決されるか
  • import時の代替パッケージ名に"."を使用スべきケース
  • packageのネストはあるのかないのか

fmt

まだ3行目を抜けられません。

import "fmt"

今度はimportされるパッケージのパスを表すであろう、"fmt"。

パスに階層らしき記述が無い以上、これはfmtが直接パッケージ名を表すんでしょうが、一体どこからインポートしているのか。 Hello Worldという最初の最初のコードに出てくるのだし、一般的にいう組み込みライブラリのようなものだとは思いますが、Goでは「組み込みパッケージ」とでも呼ぶんでしょうか。

以下に標準の組み込みパッケージらしき一覧があります。

しかし、標準パッケージのインポート元だとか、パスについての仕様が見つけられませんでした。 この辺りは環境のセットアップに関わってくる部分であり、今はまだ触れていないので、一旦スルーしておきましょう。

fmt についてわかったこと

  • fmtは組み込みパッケージらしい
  • 具体的なパスを指定せずにimportできる

fmt についてわからなかったこと

  • 組み込みパッケージらしきもののパスの解決はどう行われるのか

func main()

やっと5行目。

func main() {

これは先程のmainで出てきました。 Goのプログラムは、mainパッケージとmain関数の実装が必須、となっていたあのmain関数ですね。

面白いのは、関数の宣言に"func"というキーワードを使用している点。 "function"でも"def"でもなく、"func"。 pythonの"def"は短くて好きですが、初見で「define…? default…?」とわずかに迷ったので、これで全然問題ないです。 シンプルにしようという思想の現れで、分かりやすい。

func main() についてわかったこと

  • 関数の宣言は"func"で

この時点では、わからなかったことはありません。

fmt.Println()

インポートしたらしいfmtは、6行目で以下のように使用されています。

   fmt.Println("Hello, playground")

fmt.Println()となっているので、fmtパッケージが持つPrintln()という関数を使用しているのでしょう。 引数は単純な文字列のようで、関数名から察するに、標準出力に改行付きで出力している、ということでしょうか。 なぜか関数名の先頭が大文字ですが、多分何かの規則によるものなので、後で確認し忘れないようにします。

fmtパッケージについては、以下に詳細な仕様がありました。

fmtパッケージは、フォーマットI/Oを実装しており、C言語のprintfおよびscanfと似た関数を持ちます。フォーマットの「書式」はC言語から派生していますが、より単純化されています。

"フォーマットI/O"とは、何らかのフォーマットに基づいたインプット・アウトプットということでしょうか。 推測した用途より、fmtパッケージ自体の用途はもう少し幅広いようです。

Println関数の部分を見ると、概ね予想した通りですが、気になる部分があります。

この関数は、書き込んだバイト数と発生した書き込みエラーを返します。

Hello Worldコードでは、この戻り値は無視されているようですが、バイト数とエラーの両方を返す関数とはどういうことでしょうか。 pythonのように、関数から気軽に複数の戻り値を返せるということかも知れません。 あまりこの複数の戻り値という仕組みに慣れていないので、うまく慣れていく必要がありそうです。 しかし、エラーも戻り値で返すというのは、エラー処理をどうやれば良いのか、ぱっとイメージできません。

fmt.Println() についてわかったこと

  • fmtは何らかのフォーマットのI/Oを担うパッケージ
  • Goの関数は複数の戻り値を持つ
  • 戻り値にエラーが含まれることがある

fmt.Println() についてわからなかったこと

  • 複数の戻り値の受け方
  • 複数の戻り値の返し方
  • 戻り値のエラー情報を使ったエラー制御

Hello Worldまとめ

たった7行のコードですが、ひとつひとつ理解して進めようとすると、なかなか時間がかかります。 でも、これまでこういう進め方をしてこなかったロスを取り戻せているようで、かなり楽しいです。

次は、もう少し基本的な文法などを見ていきます。