A Tour of Go vol.5 (Range / Map)
Range
スライスやマップの要素についてindexとあわせて1つずつ返す。
var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} for i, v := range pow { fmt.Printf("index:%d value:%d\n", i, v) }
第一引数のindexについては"_"を使うことで破棄することもできる。
Maps
キーと値とを関連付ける(マップする)。
ゼロ値はnil
type Vertex struct { Min, Max int64 } // キーがString型 値がVertex型のマップ型 var m map[string]Vertex func main() { m = make(map[string]Vertex) m["Size Range"] = Vertex{ 40, 74, } fmt.Println(m["Size Range"]) }
初期化
var g = map[string]Vertex{ "Kids Size Range": {40, 60}, "Teens Size Range": {61, 100}, }
値の確認
elem, ok := m[key]
マップmにkeyがあれば2つ目の返却値でtrueが返ってくる。
クロージャ
クロージャーの説明が1番わかりやすかった記事
なぜクロージャ(Closure)と言うのか?
関数が自信のスコープ外から変数を参照し、変更を与える場合にその関数のことをクロージャーと見なされる。
func adder() func(int) int { sum := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } }
上記のコードでadder関数がクロージャーである無名関数を返却している。
無名関数が格納された変数posとnegはそれぞれadder関数の持つsum変数を参照し、値を操作している。
この時"posとnegは変数sumへバインドされている"と表現する。
A Tour of GoのBasicsが完了しました!!
Next: Methods and interfaces
Last: Concurrency
A Tour of Go vol.4 (Pointer / Array / Slice)
ここから本格的な学び
Pointer
値のメモリのアドレスのこと。
type
既存の型や型リテラルに別名をつけることができる。
type Vertex struct { X int Y int }
上記はstruct型をVertexという別名の型にしている。
typeについては下記の記事が言語仕様に則って説明してくれている。
Goを学びたての人が誤解しがちなtypeと構造体について #golang
Array
[n]T
n:配列の要素数
T:配列の型
配列の長さは変えることができない。
Slice
スライスは配列への参照のようなものであり、スライスの要素変更は元の配列に影響する。
よって同じ元となる配列を共有している他のスライスは、それらの変更が反映される。
ゼロ値はnil。
型
[]T
固定長の配列に対し、スライスは可変長。
primes := [6]int{1,2,3,4,5,6} var s []int = primes[1:4] //2, 3, 4
例)構造体を要素とする配列
複数のフィールドを持つオブジェクトを要素とする配列は下記の用に作る。
s := []struct { i int b bool }{ {2, true}, {3, false}, {5, true}, }
capacity
スライスはスライスの最初の要素から数えて、元となる配列の要素数をcapacityとして持つ。
スライス s の長さと容量は len(s) と cap(s) という式を使用して得る。
a := make([]int, 5) // len=5 cap=5 [0 0 0 0 0] b := make([]int, 0, 5) // len=0 cap=5 [] c := b[:2] // len=2 cap=5 [0 0] d := c[2:5] // len=3 cap=3 [0 0 0]
要素の追加
スライスへの新しい要素の追加はappend関数を使う。
var s []int // cap=0 [] s = append(s, 2, 3) // len=2 cap=2 [2 3]
A Tour of Go vol.3
For Continued
初期化と後処理ステートメントは省略可能。
for ; sum < 1000; { sum += sum }
whileはないので繰り返し処理は全てforで記述が統一される。
if
func pow(x, n, lim float64) float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
ifステートメントで宣言した変数はifスコープ内のみ有効なので注意する。
Switch
Switch文ではcaseの条件が一致した時点で自動的にbreakされる。
また条件なしで記述することも可能。
switch { case t.Hour() < 12: fmt.Println("Good morning!") case t.Hour() < 17: fmt.Println("Good afternoon.") default: fmt.Println("Good evening.") }
Defer
defer へ渡した関数の実行を、呼び出し元の関数の終わり(returnする)まで遅延させる。
使い所
func main() { file, err := os.Open("hoge.txt") if err != nil { fmt.Println("File open error: ", err) return } buf := make([]byte, 1024) for { n, err := file.Read(buf) if n == 0 { break } if err != nil { fmt.Println("File read error: ", err) return } fmt.Print(buf[:n]) } file.Close() }
上のプログラムの問題はエラーが発生した場合にファイルがCloseされないということ。
こういうところでDeferを使うケースが多いようだ。
func main() { file, err := os.Open("hoge.txt") if err != nil { fmt.Println("File open error: ", err) return } defer file.Close() // <- ここでCloseを遅延実行する buf := make([]byte, 1024) for { // 省略 } }
例外としてos.Exitを使った場合はdeferは実行されない。
digest認証
digest認証とはまずブラウザからサーバへユーザ名やパスワードなどをそのまま送らずに、その場で生成したランダムな文字列などとともにハッシュ値に変換してから送る。
サーバ側では予め登録されたユーザ名・パスワードなどから同様の手順でハッシュ値を計算し、両者が一致すれば認証成功とみなす認証方法のこと。
ハッシュ値に変換して送信する処理
function getHash(pw) { const salt = 'xxxxxxxxxx' const crypto = require('crypto') const hashsum = crypto.createHash('sha512') hashsum.update(pw + salt) return hashsum.digest('hex') }
入力されたパスワードにsaltを付加してハッシュ関数SHA-2のバリエーションのうちの1つであるSHA-512方式で暗号化した後、16進数でハッシュを返却。
ソルト
パスワードの前後に付加することで予め任意の文字列をハッシュ値に変換しておき、標的のパスワードのハッシュ値と比較することでパスワードを推察するレインボー攻撃の対策となる。
crypto
暗号化標準モジュール
sha512
暗号学的ハッシュ関数。
入力値に対し、常に同じ値を出力する。
入力値から同じ値を算出するのは簡単だが、その逆、結果から元の値を導き出すのは難しい。
HA-2はSHA-224、SHA-256、SHA-384、SHA-512、SHA-512/224、SHA-512/256の6つのバリエーションを持ち、ハッシュ長は224、256、384、512ビットのいずれか。
function addUser(userid, passwd, callback) { const hash = getHash(passwd) const token = getAuthToken(userid) const regDoc = {userid, hash, token, friends: {}} userDB.insert(regDoc, (err, newdoc) => { if (err) return callback(null) callback(token) }) } function login(userid, passwd, callback) { const hash = getHash(passwd) const token = getAuthToken(userid) getUser(userid, (user) => { if (!user || user.hash !== hash) { return callbask(new Error('認証エラー'), null) } user.token = token updateUser(user, (err) => { if (err) return callback(err, null) callback(null, token) }) }) }
ユーザーを登録する際にハッシュとトークンをDBに保存しておく。
ログインする際にはユーザーのハッシュが一致しなければ認証エラーとする。
ハッシュが一致した場合はトークンを更新する。
ハッシュはログイン後の行動の中で認証が必要になった際に使用する。
PEGを使ってWikiシステム作ってみた
JavaScript用のパーサージェネレーターのPEG.jsを使ってWiki記法をReactオブジェクトに変換してWikiシステムを作ってみた。
PEG文法で記法のルールを書いた`pegjs`という拡張子のファイルを用意し、
`pegjs -o src/wiki_parser.js src/wiki_parser.pegjs`コマンドを打つと文章を変換する処理をJavaScriptで書き直したファイル(wiki_parser.js)がsrc配下に生成される。
martiniからnegroniが生まれた背景とリフレクション
弊社ではGoのWebフレームワークの中でnegroniを採用している。
negroni
net/httpと直接結びついて動作する、ミドルウェアにフォーカスされたライブラリ。
セッション管理したりユーザ認証の際に利用されることが多い。
negorniでは `gin`というコマンドを使う。
ginコマンドではソースコードをビルドしてバイナリをたちあげる作業を行う。
negroniは変更をlistenしているのでソースコードを変更して保存したタイミングで更新してくれるために開発で便利。
negroniの誕生背景
negroniの作者はnegroniを作る前にmartiniというライブラリを作っていた。
martiniはリフレクションを良く使うライブラリで、そのためにGoにしては処理が遅いが、裏でよろしくやってくれて便利だった。
これはauto magicと称された。
しかし、Goの文化的に利用する側が何が行われているのか把握できないブラックボックスはよくないという批判があった。
これをうけ、作者が新しく作ったのがnegroni。
フレームワークを比較している記事
Go Web Frameworks 比較qiita.com
型
型には名前を持つ型と名前を持たない型がある。
名前を持つ型
type Team struct
名前を持たない型
`[]byte` や `*int` など型の名前と記号によって表される。
map[string]string interface{}
複数の型をうけいれる型
型には複数の型を受け入れるインターフェース型がある。
fmt.Prinflnが例として挙げられる。
func Println(a ...interface{})(n int, err error)
型の中でも静的な型と動的な型があるが、インターフェース型はどちらも受け入れることができる。
var n int = 42 // nの(静的な)型はint var s string = "hello" // sの(静的な)型はstring var v interface{} // vの(静的な)型はinterface{} v = n // vの(静的な)型はinterface{}、動的な型はint v = s // vの(静的な)型はinterface{}、動的な型はstring
reflect機能
reflectパッケージを使用すると変数の型についての情報を取得することができる。
package main import ( "fmt" "reflect" ) func main() { type MyInt int var x MyInt v := reflect.ValueOf(x) // ValueOfでreflect.Value型のオブジェクトを取得 fmt.Println(v.Type()) // Typeで変数の型を取得 fmt.Println(v.Kind()) // Kindで変数に格納されている値の種類を取得 } // main.MyInt // int
インターフェース型の場合は動的な型の情報も取得することができる。
値を格納していない場合にはメソッドがpanicを起こすので値が格納されているか否かはIsValidメソッドでチェックする。
func HasField(v interface{}, name string) bool { rv := reflect.ValueOf(v) // ポインタ型であるか if rv.Kind() == reflect.Ptr { // ポインタの要素の型を返す rv = rv.Elem() } // 構造体であるか if rv.Kind() != reflect.Struct { return false } // 引数に渡された項目がオブジェクトの項目に値があるか判定して返す return rv.FieldByName(name).IsValid() }
reflectについて詳しいサイト
reflect パッケージ - golang.jp
A Tour of Go vol.2
types
論理値型(ゼロ値:false)
・bool
文字列型(ゼロ値:"")
・string
数値型(ゼロ値:0)
・unit
・uint8 符号なし 8-ビット 整数 (0 to 255)
・uint16 符号なし 16-ビット 整数 (0 to 65535)
・uint32 符号なし 32-ビット 整数 (0 to 4294967295)
・uint64 符号なし 64-ビット 整数 (0 to 18446744073709551615)
・int
・int8 符号あり 8-ビット 整数 (-128 to 127)
・int16 符号あり 16-ビット 整数 (-32768 to 32767)
・int32 符号あり 32-ビット 整数 (-2147483648 to 2147483647)
・int64 符号あり 64-ビット 整数 (-9223372036854775808 to 9223372036854775807)
・float32 IEEE-754 32-ビット 浮動小数値
・float64 IEEE-754 64-ビット 浮動小数値
・complex64 float32の実数部と虚数部を持つ複素数
・complex128 float64の実数部と虚数部を持つ複素数
・byte uint8の別名(エイリアス)
・rune int32の別名(エイリアス)
使い分け方は記事によってはuintを使えと書かれていたり、整数は基本intだ!などバラバラです。
自社のコードでは整数ではInt64, 浮動小数ではflat64を使っていたのであわせます。
Type conversions
型変換を行う際は新しい変数を用意し、その変数に変換する先の型を宣言する。
var x, y int = 3, 4 var f float64 = math.Sqrt(float64(x*x + y*y)) var z uint = uint(f)
int -> float64 -> uint
goでは型を明示しない場合は型推論が行われる。
Constants
constは":="を使った宣言はできない。