- 投稿日:2020-05-23T17:34:22+09:00
【Golang】ポインタ
【Golang】ポインタ
Golangの基礎学習〜Webアプリケーション作成までの学習を終えたので、復習を兼ねてまとめていく。 基礎〜応用まで。
package main //ポインタ //& アドレス //* アドレスの実態 //*int ポインタ型 この中にはアドレスを入れる //関数で変数の値を変えたい場合は、関数の引数はポインタ型(メモリーのアドレス)を受け取るようにする。 import ( "fmt" ) func double(i int){ i = i * 2 } //ポインタ型を引数 func double2(i *int){ *i = *i * 2 } func main() { var n int = 100 fmt.Println(n) //>>100 fmt.Println(&n) //メモリのアドレスを表示 //>>0xc000136008 //2倍にする関数 double(n) fmt.Println(n) //値渡しされる為、変わらない //>>100 //ポインタ型 //メモリーのアドレスの事 //nのアドレスをpに定義(nを参照している) //&を使って任意の型から、そのポインタ型を生成できる。 //&はアドレス演算子と呼ばれる。 var p *int = &n //アドレスのアドレス? fmt.Println(&p) //メモリーを表示 fmt.Println(p) //>>0xc000136008 //メモリーの中身を表示 fmt.Println(*p) //>>100 //ポインタ型を引数に受けとるので、アドレスを渡す //double2(n)はnがポインタ型でないので、エラー double2(&n) //デリファレンス //参照渡しになるので変化する。 fmt.Println(n) //>>200 //nの参照のpなので400になる。 double2(p) fmt.Println(*p) //>>400 //こんなこともできる //nのアドレス fmt.Println(&n) //nのアドレスの実態 fmt.Println(*&n) //nのアドレスの実態のアドレス fmt.Println(&*&n) }
- 投稿日:2020-05-23T09:31:38+09:00
エディタで複数のファイルを簡単にコピーするコマンドをGoで作った
エディタを使って複数のファイルを簡単にコピーするコマンドmcpを作りました。
このコマンドはファイルをエディターで一括リネームするツールをGo言語で作った! ― 機能を増やさない信念と、OSSとの付き合い方で
mmv
を知ってしばらく使っていたら、リネームではなくコピーもほしいなと思い作りました。使い方
ファイル名を引数で渡すと
$EDITOR
に設定されているエディタが起動、ファイル名を編集して保存することで編集後のパスにファイルがコピーされます。
ファイル名だけではなく、ワイルドカードも使えます。$ mcp *コピー元がディレクトリの場合はそのディレクトリをまるごとコピーします。
cp -r
に相当します。さいごに
ブログに記載されている機能を増やさない信念は個人的にともて感銘を受けた言葉です。
これまでツールをいくつか作ってきましたが、どれも機能をたくさん盛り込んできました。一つのツールでなるべくたくさんのことができたほうがよいと思っていたからです。しかし、ツールが多機能になっていくとその分メンテがめちゃくちゃ大変になっていきます。
便利さと大変さは比例するんだなってこのブログを読んでから気づきました。今後は新しいものを作るとき、機能を増やさない信念を念頭に入れていこうと思います。
- 投稿日:2020-05-23T08:26:27+09:00
Windows 版の TinyGo で SAMD5x CPU の浮動小数点演算でクラッシュする問題への対策
Windows 版の TinyGo のみ SAMD5x の浮動小数点演算を使おうとするとクラッシュします。
この記事の対象は今のところ、 Windows 版の 0.12 ~ 0.13.x です。
他 OS 版はもともとクラッシュしないはずです。
この問題はおそらく TinyGo 0.14 で修正されますが、それまでの間の対策を記載します。対策
以下のいずれかの対策をすることで、クラッシュしなくなります。
対策 1 : FPU を有効にする
以下を好きなファイル名 (*.go) で main パッケージのディレクトリに置いてください。
main() 関数が始まる前に FPU を有効化するコードが処理されクラッシュしなくなります。
ビルドする対象のプロジェクト毎に実施する必要があります。gits にも置きました。
https://gist.github.com/sago35/95c2213093ac3565865be310e5c432a2enablefpu.go// +build atsamd51 // Put this file in the main package directory // https://github.com/tinygo-org/tinygo/issues/944#issuecomment-597065613 // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.ddi0439b/BEHBJHIG.html package main import ( "runtime/volatile" "unsafe" ) func init() { enableCortexM4FPU() } func enableCortexM4FPU() { cpacraddr := (*uint32)(unsafe.Pointer(uintptr(0xE000ED88))) tmp := *cpacraddr tmp |= (0xF << 20) volatile.StoreUint32(cpacraddr, tmp) }対策 2 :
-mfloat-abi=soft
の設定をする他の環境 (linux や macOS や docker) と同じバイナリを生成できます。
公式にこの設定が有効になるのはおそらく tinygo 0.14 になってからだと思われます。ただ、自前で LLVM と tinygo.exe をビルドする必要があるので現実的には厳しいと思います。
以下の PR にて対策が行われつつあります。
- arm: make FPU configuraton consistent
原因
※本項目は読み飛ばして OK です
原因をざくっと書くと、
- Windows 版は LLVM 自前ビルドしていて
-mfloat-abi=soft
がデフォルトオプションになっていない
- linux や macos はバイナリインストール版の LLVM を使っていてデフォルトとなっている
- ARM マイコン上の設定で FPU を有効にしていない状態で、 FPU を使った浮動小数点演算を実施する
という組み合わせになっているため、です。
FPU を使うコード (例:x += 0.1
等) があった場合に、 FPU を有効にしていないのに FPU を使おうとするためクラッシュします。詳細は以下の Issue / PR に記載されていますので、気になる人は読んでみてください。
- SAM D5x based boards crash when using floating point operations
- runtime: Enable FPU of SAM D5x
- arm: make FPU configuraton consistent
まとめ
とりあえずは、
enablefpu.go
を置く対策をしておくと良いです。
tinygo 0.14 で修正されているとよいですね。
- 投稿日:2020-05-23T00:58:22+09:00
【初心者向け】日本語で書いたGoのコードを丁寧に解説してみた
TL;DR
天から授かったアイデア 「ミーティングをシミュレーションせよ」
+
アホな自分 「日本語でGoを書いたらどうなるのかな」
合体 → なぜか、Goの文法を丁寧に説明する教育的な記事になった(気がする)
対象読者
- プログラミング知識はそこそこあるが、Goを触ったことない人
- Goの文法を知ってはいるけど、どう使うべきかわからない人
最終的な出力結果
悪いミーティングが開かれた場合
zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする!良いミーティングが開かれた場合
hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする!実装を解説していく
私が書いたコードを私が解説する記事がついに始まりました。
何事も最初は
main()
関数から見ていくのが理解への近道です。main関数
main.gofunc main() { 参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, } 次のミーティングの日時 := time.Now().AddDate(0, 0, 7) 良いミーティング := 良いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(良いミーティング) 悪いミーティング := 悪いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(悪いミーティング) }はい。未定義の型がたくさん出てきたので、細かく見ていきましょう。
main.go(main)参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, }この
参加者
はメンバー
構造体のポインタの配列です。
メンバー
構造体は、次のような定義です。main.go// メンバーID は、メンバーに与えられる固有のIDです. type メンバーID string // メンバー名 は、メンバーの名前を表す型です. type メンバー名 string // メンバー は、会議に参加したりしなかったりするメンバーです. type メンバー struct { ID メンバーID 名前 メンバー名このように、
string
であるようなID
や名前
のために、メンバーID
やメンバー名
の型を定義しています。
一見、冗長に見えますが、string
のままだと、何かしらの関数の引数で複数のstring
を渡す場合などで起こる他の変数との混在を、コンパイラレベルでエラーとして検知できるようになります。
見た目以上に便利なテクニックなので、ぜひ使ってみてください。参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, }こう書くことで、
参加者
という変数には、ID = "zawawa"
のメンバー
型のインスタンスのポインタと、ID = "hoge"
のメンバー
型のインスタンスのポインタの配列が格納されることになります。そして、次の行がこちらです。
次のミーティングの日時 := time.Now().AddDate(0, 0, 7)Goの標準パッケージの一つである
time
パッケージでtime.Now()
関数によって、現在時刻を表すtime.Time
インスタンスを生成し、それに7日足して、一週間後のtime.Time
構造体のインスタンスを次のミーティングの日時
変数に格納しています。そして、実際に
良いミーティングを作る
で得られる良いミーティング
インスタンスをミーティングを行う
関数に渡しています。良いミーティング := 良いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(良いミーティング)
良いミーティング
インスタンスは、ミーティング
インターフェイスの実装です。どういうことやねん、となると思うので、
ミーティング
インターフェイスを見てみます。// ミーティング は、ミーティングを表すインターフェイスです. ここでは、ミーティングとは、次の行動を出力するものだけを指すこととします. type ミーティング interface { 意見を出し合う() []意見 意見から次にやることを決める(出てきた意見 []意見) ([]*次にやること, error) }
ミーティング
インターフェイスは、先程出てきた 構造体 とは異なり、具体的な存在では有りません。 これこれの関数を実装した構造体を ミーティング インターフェイスとみなせるよ という宣言です。
ちなみに、ここで出てきた意見
と次にやること
は次のような定義です。// 意見 は、ミーティング中に出てくる意見のことです. 次やることを決めるための材料になります. type 意見 string // 具体的な行動 は、「何を」するかを表現する型です. type 具体的な行動 string // 次にやること は、次にやることです. 誰がやるか、何をするか、いつまでにするかをしっかり決めましょう. type 次にやること struct { 誰が メンバーID 何を 具体的な行動 いつまで *time.Time }ようするに、
ミーティング
インターフェイスを満たすためには、次の2つの関数を持つ構造体を作って上げればよいことになります。
意見
の配列を出力する意見を出し合う()
関数意見
の配列を材料に、次にやること
の配列(とエラー)を出力する関数
ミーティング
インターフェイスについてある程度眺めたところで、ミーティングを行う
関数に戻ってみましょう。
ミーティングを行う
関数func ミーティングを行う(ミ ミーティング) { 出し合った意見 := ミ.意見を出し合う() 次にやることリスト, エラー := ミ.意見から次にやることを決める(出し合った意見) if エラー != nil { log.Fatalf("意見から次にやることを決めてるときに、エラーが起きました. エラー = %#v", エラー) } for _, 次にやること := range 次にやることリスト { fmt.Println(次にやること.文字列にする()) } }なんだか、ごちゃごちゃしてるので、一行ずつ見ていきましょう。
出し合った意見 := ミ.意見を出し合う()これによって、
ミーティング
インターフェイスの意見を出し合う
関数が呼び出され、意見
の配列が出力されることになりますね。
そして、出し合った意見
は、ミーティング
インターフェイスのもう一つの関数である意見から次にやることを決める
関数に渡され、最終的に次にやること
の配列が返されます。次にやることリスト, エラー := ミ.意見から次にやることを決める(出し合った意見) if エラー != nil { log.Fatalf("意見から次にやることを決めてるときに、エラーが起きました. エラー = %#v", エラー) }ここで、 Golang独自のエラーハンドリングが現れましたね。
エラー
はerror
インターフェイスを実装したインスタンスです。
error
インターフェイスとは、Golangに組み込まれたインターフェイスで、string
を返すError()
関数を持つものを言います。意外に単純ですね。builtin.go// The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. type error interface { Error() string }
エラー
そのものは、構造体へのポインタなので、「何も指し示さない」nil
という値も取ります。エラー == nil
のときは、 エラーが起きていない という意味になります。if エラー != nil { // 何かしらのエラーがあったってこと。 }ミーティングの末得られた
次にやることリスト
にある各次にやること
構造体を文字列にして、標準出力しているのがこちらです。for _, 次にやること := range 次にやることリスト { fmt.Println(次にやること.文字列にする()) }
ミーティングを行う関数
がやっていることまとめ
ミーティング
インターフェイス(を実装した構造体のポインタ)を受け取り、意見を出し合う
関数を実行して意見
を出し合い、意見から次にやることを決める
関数をでそれらを次にやること
に変換するという流れになります。
ミーティング
インターフェイスはなぜ必要なのか?なんで、
ミーティング
インターフェイスなんてものを宣言したのでしょうか?
私は、ミーティングをシミュレートするコードを書きたいと考えたときに、まず、 「ミーティングとは何か?」 を考えました。
そのときに思い浮かぶものは、 ミーティングがどういう流れで行われ、どういう機能があるかということです。大概のミーティングでは、最初にアジェンダ的なものがあり、そこから誰かがファシリをしながら、全体で議論をしていき、最終的にネクストアクション(ここでは
次にやることリスト
) にたどり着くはずです。そのような、 一般的なミーティングで行われる行動を表すのに用いるのが インターフェイス です。
個別のミーティングがどういう中身で、どういう結論を導くかという具体的な部分を考えるのではなく、 ミーティングが持つ 抽象的な性質 にフォーカスしているのです。
(もちろん、このインターフェイス自体もミーティングの一側面を表しているに過ぎないのでご了承を)
ミーティング
インターフェイスの実装を一つ見てみる(悪いミーティング
)// 悪いミーティング は、目指してはいけない悪いミーティングです. type 悪いミーティング struct { 参加者 []*メンバー 次のミーティングの日時 *time.Time } func 悪いミーティングを作る(参加者 []*メンバー, 次のミーティングの日時 *time.Time) ミーティング { return &悪いミーティング{ 参加者: 参加者, 次のミーティングの日時: 次のミーティングの日時, } } func (ミ *悪いミーティング) 意見を出し合う() []意見 { var 発散しがちな議論 []意見 // 時間だけは伸びる. for i := 0; i < 20; i++ { if i%5 == 0 { 発散しがちな議論 = append(発散しがちな議論, 意見("良い意見")) continue } 発散しがちな議論 = append(発散しがちな議論, 意見("見当外れの意見")) } return 発散しがちな議論 } func (ミ *悪いミーティング) 意見から次にやることを決める(出てきた意見 []意見) ([]*次にやること, error) { // 出てきた意見の吟味は特にしない var 次にやることリスト []*次にやること for _, 意見の一つ := range 出てきた意見 { やること := &次にやること{ // 誰かの負担が大きい 誰が: ミ.参加者[0].ID, 何を: 意見をやることに変換する(意見の一つ), // 特にいつまでと決めない いつまで: nil, } 次にやることリスト = append(次にやることリスト, やること) } return 次にやることリスト, nil }詳細な説明は省きますが、注目してほしいのは次の点です。
悪いミーティングを作る
がコンストラクタの役割を果たし、悪いミーティング
構造体のポインタをミーティング
インターフェイスとして返すfunc 悪いミーティングを作る(参加者 []*メンバー, 次のミーティングの日時 *time.Time) ミーティング { return &悪いミーティング{ 参加者: 参加者, 次のミーティングの日時: 次のミーティングの日時, } }いくつか、
悪いミーティング
構造体にセットするための引数が与えられていますが、特に重要なのは、ミーティング
インターフェイスが戻り値となっている点です。
しかし、関数の中では&悪いミーティング{}
という構造体のポインタが返却されています。ここで
悪いミーティング
構造体のポインタからミーティング
インターフェイスへの変換を行うためには、悪いミーティング
構造体がミーティング
インターフェイスが持つ関数を実装していなければなりません。Javaなどの言語では、Interfaceをclassの定義で指定して、明示的に実装を行いますが、Golangでは、そのような明示的に構造体がインターフェイスを実装する文法はありません。
このようなコンストラクタを書いてあげると、コンパイラが暗黙的に構造体がインターフェイスを実装しているかを確かめてくれるのです。
悪いミーティング
を開催してみたさて、ようやく実装できたので、実行してみます。
main.gofunc main() { 参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, } 次のミーティングの日時 := time.Now().AddDate(0, 0, 7) 悪いミーティング := 悪いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(悪いミーティング) }結果: 悪いミーティングをすると、誰か一人が大量の見当外れの意見を基にしたネクストアクションをさせられることに
非常に恐ろしいシミュレーション結果になってしまいましたね。みなさんのミーティングはこうならないことを祈ります。
zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする!まとめ
思いつきで書き始めた割になかなかの分量になってしまうのが私の癖ですが、いかがでしたでしょうか。
特に、
悪いミーティング
は具体的な何かを指すものではなく、仮想上のミーティングなので、関係者よろしくお願いします。Qiitaの自分のページ( @zawawahoge )を見ると、
のように、業務で使ってる Golangの記事ばかりをLGTMしてる最近の私ですが、元々はPython大好き少年でした。
投稿した記事もPythonのものが多かったですが、そろそろGolangもいい感じに使えるようになってきた(気がするだけかもしれない)ので、そろそろ記事にしたいなーと考えていたところでした。実は、今回のこの記事は、自分なりにここ半年ほどで実務経験で学んだテクニックなどが詰まっていて、ひそかに書いてよかったと思っています。
最初の発端はともかく、この記事を読んで、 「Goって面白そうだな」と感じて、触ってみてくれる人がいると、とても嬉しいです 。
長々と読んでくださりありがとうございました。
面白いと思ってもらえたら、LGTMよろしくお願いします!
Twitter( @zawawahoge ) もやってますので、ぜひぜひご感想などいただければ幸いです。
- 投稿日:2020-05-23T00:58:22+09:00
ミーティングをシミュレーションするGoのプログラムを日本語で書いた
TL;DR
天から授かったアイデア 「ミーティングをシミュレーションせよ」
+
アホな自分 「日本語でGoを書いたらどうなるのかな」
合体 → なぜか、Goの文法を丁寧に説明する教育的な記事になった(気がする)
対象読者
- プログラミング知識はそこそこあるが、Goを触ったことない人
- Goの文法を知ってはいるけど、どう使うべきかわからない人
最終的な出力結果
悪いミーティングが開かれた場合
zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする!良いミーティングが開かれた場合
hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! hoge が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする! zawawa が 良い意見 を基にした具体的な行動 を 2020-05-29 22:58:55.830343 +0900 JST までにする!実装を解説していく
私が書いたコードを私が解説する記事がついに始まりました。
何事も最初は
main()
関数から見ていくのが理解への近道です。main関数
main.gofunc main() { 参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, } 次のミーティングの日時 := time.Now().AddDate(0, 0, 7) 良いミーティング := 良いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(良いミーティング) 悪いミーティング := 悪いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(悪いミーティング) }はい。未定義の型がたくさん出てきたので、細かく見ていきましょう。
main.go(main)参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, }この
参加者
はメンバー
構造体のポインタの配列です。
メンバー
構造体は、次のような定義です。main.go// メンバーID は、メンバーに与えられる固有のIDです. type メンバーID string // メンバー名 は、メンバーの名前を表す型です. type メンバー名 string // メンバー は、会議に参加したりしなかったりするメンバーです. type メンバー struct { ID メンバーID 名前 メンバー名このように、
string
であるようなID
や名前
のために、メンバーID
やメンバー名
の型を定義しています。
一見、冗長に見えますが、string
のままだと、何かしらの関数の引数で複数のstring
を渡す場合などで起こる他の変数との混在を、コンパイラレベルでエラーとして検知できるようになります。
見た目以上に便利なテクニックなので、ぜひ使ってみてください。参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, }こう書くことで、
参加者
という変数には、ID = "zawawa"
のメンバー
型のインスタンスのポインタと、ID = "hoge"
のメンバー
型のインスタンスのポインタの配列が格納されることになります。そして、次の行がこちらです。
次のミーティングの日時 := time.Now().AddDate(0, 0, 7)Goの標準パッケージの一つである
time
パッケージでtime.Now()
関数によって、現在時刻を表すtime.Time
インスタンスを生成し、それに7日足して、一週間後のtime.Time
構造体のインスタンスを次のミーティングの日時
変数に格納しています。そして、実際に
良いミーティングを作る
で得られる良いミーティング
インスタンスをミーティングを行う
関数に渡しています。良いミーティング := 良いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(良いミーティング)
良いミーティング
インスタンスは、ミーティング
インターフェイスの実装です。どういうことやねん、となると思うので、
ミーティング
インターフェイスを見てみます。// ミーティング は、ミーティングを表すインターフェイスです. ここでは、ミーティングとは、次の行動を出力するものだけを指すこととします. type ミーティング interface { 意見を出し合う() []意見 意見から次にやることを決める(出てきた意見 []意見) ([]*次にやること, error) }
ミーティング
インターフェイスは、先程出てきた 構造体 とは異なり、具体的な存在では有りません。 これこれの関数を実装した構造体を ミーティング インターフェイスとみなせるよ という宣言です。
ちなみに、ここで出てきた意見
と次にやること
は次のような定義です。// 意見 は、ミーティング中に出てくる意見のことです. 次やることを決めるための材料になります. type 意見 string // 具体的な行動 は、「何を」するかを表現する型です. type 具体的な行動 string // 次にやること は、次にやることです. 誰がやるか、何をするか、いつまでにするかをしっかり決めましょう. type 次にやること struct { 誰が メンバーID 何を 具体的な行動 いつまで *time.Time }ようするに、
ミーティング
インターフェイスを満たすためには、次の2つの関数を持つ構造体を作って上げればよいことになります。
意見
の配列を出力する意見を出し合う()
関数意見
の配列を材料に、次にやること
の配列(とエラー)を出力する関数
ミーティング
インターフェイスについてある程度眺めたところで、ミーティングを行う
関数に戻ってみましょう。
ミーティングを行う
関数func ミーティングを行う(ミ ミーティング) { 出し合った意見 := ミ.意見を出し合う() 次にやることリスト, エラー := ミ.意見から次にやることを決める(出し合った意見) if エラー != nil { log.Fatalf("意見から次にやることを決めてるときに、エラーが起きました. エラー = %#v", エラー) } for _, 次にやること := range 次にやることリスト { fmt.Println(次にやること.文字列にする()) } }なんだか、ごちゃごちゃしてるので、一行ずつ見ていきましょう。
出し合った意見 := ミ.意見を出し合う()これによって、
ミーティング
インターフェイスの意見を出し合う
関数が呼び出され、意見
の配列が出力されることになりますね。
そして、出し合った意見
は、ミーティング
インターフェイスのもう一つの関数である意見から次にやることを決める
関数に渡され、最終的に次にやること
の配列が返されます。次にやることリスト, エラー := ミ.意見から次にやることを決める(出し合った意見) if エラー != nil { log.Fatalf("意見から次にやることを決めてるときに、エラーが起きました. エラー = %#v", エラー) }ここで、 Golang独自のエラーハンドリングが現れましたね。
エラー
はerror
インターフェイスを実装したインスタンスです。
error
インターフェイスとは、Golangに組み込まれたインターフェイスで、string
を返すError()
関数を持つものを言います。意外に単純ですね。builtin.go// The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. type error interface { Error() string }
エラー
そのものは、構造体へのポインタなので、「何も指し示さない」nil
という値も取ります。エラー == nil
のときは、 エラーが起きていない という意味になります。if エラー != nil { // 何かしらのエラーがあったってこと。 }ミーティングの末得られた
次にやることリスト
にある各次にやること
構造体を文字列にして、標準出力しているのがこちらです。for _, 次にやること := range 次にやることリスト { fmt.Println(次にやること.文字列にする()) }
ミーティングを行う関数
がやっていることまとめ
ミーティング
インターフェイス(を実装した構造体のポインタ)を受け取り、意見を出し合う
関数を実行して意見
を出し合い、意見から次にやることを決める
関数をでそれらを次にやること
に変換するという流れになります。
ミーティング
インターフェイスはなぜ必要なのか?なんで、
ミーティング
インターフェイスなんてものを宣言したのでしょうか?
私は、ミーティングをシミュレートするコードを書きたいと考えたときに、まず、 「ミーティングとは何か?」 を考えました。
そのときに思い浮かぶものは、 ミーティングがどういう流れで行われ、どういう機能があるかということです。大概のミーティングでは、最初にアジェンダ的なものがあり、そこから誰かがファシリをしながら、全体で議論をしていき、最終的にネクストアクション(ここでは
次にやることリスト
) にたどり着くはずです。そのような、 一般的なミーティングで行われる行動を表すのに用いるのが インターフェイス です。
個別のミーティングがどういう中身で、どういう結論を導くかという具体的な部分を考えるのではなく、 ミーティングが持つ 抽象的な性質 にフォーカスしているのです。
(もちろん、このインターフェイス自体もミーティングの一側面を表しているに過ぎないのでご了承を)
ミーティング
インターフェイスの実装を一つ見てみる(悪いミーティング
)// 悪いミーティング は、目指してはいけない悪いミーティングです. type 悪いミーティング struct { 参加者 []*メンバー 次のミーティングの日時 *time.Time } func 悪いミーティングを作る(参加者 []*メンバー, 次のミーティングの日時 *time.Time) ミーティング { return &悪いミーティング{ 参加者: 参加者, 次のミーティングの日時: 次のミーティングの日時, } } func (ミ *悪いミーティング) 意見を出し合う() []意見 { var 発散しがちな議論 []意見 // 時間だけは伸びる. for i := 0; i < 20; i++ { if i%5 == 0 { 発散しがちな議論 = append(発散しがちな議論, 意見("良い意見")) continue } 発散しがちな議論 = append(発散しがちな議論, 意見("見当外れの意見")) } return 発散しがちな議論 } func (ミ *悪いミーティング) 意見から次にやることを決める(出てきた意見 []意見) ([]*次にやること, error) { // 出てきた意見の吟味は特にしない var 次にやることリスト []*次にやること for _, 意見の一つ := range 出てきた意見 { やること := &次にやること{ // 誰かの負担が大きい 誰が: ミ.参加者[0].ID, 何を: 意見をやることに変換する(意見の一つ), // 特にいつまでと決めない いつまで: nil, } 次にやることリスト = append(次にやることリスト, やること) } return 次にやることリスト, nil }詳細な説明は省きますが、注目してほしいのは次の点です。
悪いミーティングを作る
がコンストラクタの役割を果たし、悪いミーティング
構造体のポインタをミーティング
インターフェイスとして返すfunc 悪いミーティングを作る(参加者 []*メンバー, 次のミーティングの日時 *time.Time) ミーティング { return &悪いミーティング{ 参加者: 参加者, 次のミーティングの日時: 次のミーティングの日時, } }いくつか、
悪いミーティング
構造体にセットするための引数が与えられていますが、特に重要なのは、ミーティング
インターフェイスが戻り値となっている点です。
しかし、関数の中では&悪いミーティング{}
という構造体のポインタが返却されています。ここで
悪いミーティング
構造体のポインタからミーティング
インターフェイスへの変換を行うためには、悪いミーティング
構造体がミーティング
インターフェイスが持つ関数を実装していなければなりません。Javaなどの言語では、Interfaceをclassの定義で指定して、明示的に実装を行いますが、Golangでは、そのような明示的に構造体がインターフェイスを実装する文法はありません。
このようなコンストラクタを書いてあげると、コンパイラが暗黙的に構造体がインターフェイスを実装しているかを確かめてくれるのです。
悪いミーティング
を開催してみたさて、ようやく実装できたので、実行してみます。
main.gofunc main() { 参加者 := []*メンバー{ {ID: "zawawa", 名前: "働き者のざわ"}, {ID: "hoge", 名前: "やる気ないほげ"}, } 次のミーティングの日時 := time.Now().AddDate(0, 0, 7) 悪いミーティング := 悪いミーティングを作る(参加者, &次のミーティングの日時) ミーティングを行う(悪いミーティング) }結果: 悪いミーティングをすると、誰か一人が大量の見当外れの意見を基にしたネクストアクションをさせられることに
非常に恐ろしいシミュレーション結果になってしまいましたね。みなさんのミーティングはこうならないことを祈ります。
zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 良い意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする! zawawa が 見当外れの意見 を基にした具体的な行動 をする!まとめ
思いつきで書き始めた割になかなかの分量になってしまうのが私の癖ですが、いかがでしたでしょうか。
特に、
悪いミーティング
は具体的な何かを指すものではなく、仮想上のミーティングなので、関係者よろしくお願いします。Qiitaの自分のページ( @zawawahoge )を見ると、
のように、業務で使ってる Golangの記事ばかりをLGTMしてる最近の私ですが、元々はPython大好き少年でした。
投稿した記事もPythonのものが多かったですが、そろそろGolangもいい感じに使えるようになってきた(気がするだけかもしれない)ので、そろそろ記事にしたいなーと考えていたところでした。実は、今回のこの記事は、自分なりにここ半年ほどで実務経験で学んだテクニックなどが詰まっていて、ひそかに書いてよかったと思っています。
最初の発端はともかく、この記事を読んで、 「Goって面白そうだな」と感じて、触ってみてくれる人がいると、とても嬉しいです 。
長々と読んでくださりありがとうございました。
面白いと思ってもらえたら、LGTMよろしくお願いします!
Twitter( @zawawahoge ) もやってますので、ぜひぜひご感想などいただければ幸いです。