知識のリンク集

技術系アウトプット

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

値のメモリのアドレスのこと。

ポインターの型

変数variableのポインタは *variable型
ゼロ値はnil

&オペレータ

オペレータ(=演算子)
オペランド(=被演算子)
Goでは&オペレーターがあり、オペランドへのポインタを引き出す。

i := 42
p = &i
fmt.Println(*p) // ポインタpを通してiから値を読みだす
*p = 21         // ポインタpを通してiへ値を代入する

この時変数*pはアドレスの情報のみを持っているため、メモリに記憶する情報が少なくて済む。
大きな構造体などをそのまま変数に格納するよりもアドレスを格納した方がメモリが節約できる。

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配下に生成される。


github.com

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


リフレクション

オブジェクトがそれ自身の構造や計算上の意味を取得することを可能にする。
プログラムのソースコードコンパイルされると、プログラムの構造などの情報は低レベルコードに変換される過程で失われてしまう。
リフレクションをサポートする場合、そのような情報は生成されるコードの中にメタデータとして保存される。

goにはreflectというパッケージがあるが、これを説明する前に一度型についておさらいする。

型には名前を持つ型と名前を持たない型がある。

名前を持つ型

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

variable

goの変数宣言のステートメントは "var"

またはvarを省略して下記のような書き方もできる

k := 3

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は":="を使った宣言はできない。