Go言語:関数(クロージャ)

クロージャな話

  • Go言語の関数は値として扱える。
  • Go言語の関数はクロージャとして振る舞う。

試してみた

クロージャのスコープ

スコープは他の言語のそれと同じ。

** 1 1
1
** 1 1
1
** 2 1
2
** 2 2
4
** 3 1
3
** 3 3
27
** 1 1
1
** 2 4
16
** 3 27
7.625597484987e+12
** 1 1
1
** 1 1
1
** 2 1
2
** 2 2
4
** 3 4
81
** 3 81
4.434264882430377e+38
** 1 4.434264882430377e+38
1
** 2 1
2
** 3 2
9

試してみた:演習

この演習に対しての回答。

closureの演習

0 0
1 1
2 1
3 2
4 3
5 5
6 8
7 13
8 21
9 34

フィボナッチだけど、呼び出しごとに値を返せば良い、呼び出しに対してインデックスは与えられない、ということで、できるだけ簡単な実装にしてみている。

switchはまだ出てきてないけど、、、他言語と同じで、breakを書かなくてよい、という文法の理解のみで使用。

数列の0番目と1番目が固定値になるのは、フィボナッチ数 - Wikipediaから定義を拝借。

Go言語:Map

Mapの話

  • mapの型の指定は、キーと値の型をそれぞれ指定する。
    • map[キーの型]値の型という形式。
    • map[string]int みたいな。
  • 作成するには、make関数、もしくは初期化子を使う。
    • make関数、空の初期化子で作ったmapは、空。
    • 初期化前のmapは、nil
  • nilのmapに要素を割り当てられない。
    • panic: assignment to entry in nil mapになる。
  • 初期化時には、必ずキーが必要。
    • structのフィールドを明示する方法に同じ。
  • 初期化したmapの値は、指定された型のzero valueが与えられる。
  • ブラケットとキー[キー]でアクセス可能。
    • キーと値を削除するには、delete関数を使用する。
  • ブラケットでキーにアクセスした返り値の2つ目は、キーが存在するかどうかのbool値を返す。
    • キーが存在しない場合にfalse

試してみた1

mapの操作

withNilMap
map[]
0
true
withMakeMap
map[]
0
withEmptyInitMap
map[]
0
withInitMap
map[fuga:2 hoge:1]
1
1 true
0 false

今更ながら気づいた(正確じゃないかも。要確認)

  • 初期化子を使って初期化するときに、複数行で値を渡す場合、最後の要素の後にもカンマが必要。
    • ないと文法エラーになる。syntax error: need trailing comma before newline in composite literal
  • 複数の値を返り値とする場合、受け取り側の実装による挙動が、Rubyと少し違う?
    • 違うっぽい。
    • 返り値を返さない関数を、返り値を期待した使い方をすると関数() used as valueエラーになる。
    • 変数の数が返り値の数にあっていないと、assignment count mismatch: 2 = 1みたいなエラーがでる。
    • fmt.Printlnはうまく受け取ってくれる。
  • Mapの値に対するアクセスの戻り値は、以下の様な挙動っぽい。
    • 0個の返り値を関数の引数にする場合:1つ目の返り値が関数の引数になる。(fmt.Printlnの挙動から)
    • 1個の返り値を受け取る場合:1つ目の返り値のみ受け取る。

mapの初期化

コメントアウトを外すと、いい感じにエラーする。

試してみた2

演習のコード。

mapの演習

例えばrubyのハッシュの用に、新しいキーに対するカウントをインクリメントするときにも、zero valueで初期化されているので、キーの有無にかかわらずにインクリメントできる。

PASS
 f("I am learning Go!") = 
  map[string]int{"learning":1, "Go!":1, "I":1, "am":1}
PASS
 f("The quick brown fox jumped over the lazy dog.") = 
  map[string]int{"brown":1, "over":1, "lazy":1, "dog.":1, "the":1, "The":1, "quick":1, "fox":1, "jumped":1}
PASS
 f("I ate a donut. Then I ate another donut.") = 
  map[string]int{"I":2, "ate":2, "a":1, "donut.":2, "Then":1, "another":1}
PASS
 f("A man a plan a canal panama.") = 
  map[string]int{"panama.":1, "A":1, "man":1, "a":2, "plan":1, "canal":1}

Go言語:スライス、ループの演習から

演習の内容から(メモ的な)

Go言語で面白いと思うこと

リモートサーバーのライブラリを簡単にインポートできる。

import "code.google.com/p/go-tour/pic"

関数そのものを引数に渡すことができる。

func main() {
    pic.Show(Pic)
}

渡された側の実装がこちら

メモな演習の実装

func Pic(dx, dy int) [][]uint8 {
    x := make( [][]uint8, dx )
    
    for i := range x {
        y := make( []uint8, dy )

        for j := range y {
            y[ j ] = uint8( rand.Int() )
        }
        
        x[ i ] = y
    }
    
    return x
}

Go言語:for-rangeループ

for-rangeループの話

  • for-rangeループは、Slice(とかMap)の要素を簡単にループ処理する。
  • Go1.4から、インデックス/値を変数に代入する文法を省略可能。
    • Sliceの要素数分だけのループ処理を簡単にかける。
  • インデックスの値を使わずに値だけを使う場合、アンダースコア_に代入して、破棄する。
    • アンダースコア_を変数としては使えない。

試してみた

for-rangeループ

rangeWith2Var
0 10
1 20
2 30
3 40
4 50
5 60
6 70
7 80
8 90
rangeWith1VarForIndex
0
1
2
3
4
5
6
7
8
rangeWith1VarForValue
10
20
30
40
50
60
70
80
90
rangeWithoutVar
*********

コードの中のコメントを解いて、アンダースコア_を変数として使ってみるとエラーする。

./sample.go:31: cannot use _ as value

Go言語:スライス

スライスの話(go-tourは、なぜかArrayをとばしてSliceの話をする)

SliceとArrayは異なる。こちらを参照してお勉強。

あとはこちらのSlideShareを参照する。

Array

  • Go言語のArrayは長さ要素の型を明らかにしたもの
    • 値である。(C言語とは異なる。)
    • Go言語の基本が値渡しになるので、避けるには、Array全体に対してのポインターを渡す。
  • Arrayの初期化は構造体とだいたい同じ。
    • [要素数]型{ 初期化する値 }
    • 素数が必要。[2]intみたいな。
    • 素数を省略してコンパイラに委ねる場合は、[...]intのようにする。
    • 初期化子に値が渡されなかったインデックスは、zero valueで初期化される。(intなら0とか、stringなら空文字とか)

Slice

  • Arrayの抽象的なもの(らしい)(抽象的とは?)
    • Go言語ではArrayはあまり使われずに、Sliceが使われる。
  • Sliceは、Arrayへのポインター長さ容量で構成される。
    • Arrayへのポインターを持っているので、要素を変更すると、参照元に影響がある。
    • 長さは、Array自体の長さ。
  • Sliceの初期化は、Arrayのそれとだいたい同じ。
    • []型{ 初期化する値 }
    • 素数がいらない。...も書かない。
  • 初期化時に値が渡されない場合、nilになる。
    • nilの長さは、0。
    • nilの容量は、0。
  • 初期化の文法を使わずに、make関数を使うことでも作成可能。
    • make([]T, len, cap)
    • capを省略した場合は、len == capになる。
    • caplenよりも短い場合、エラーする。
    • lenで指定された長さ分(0からlen個分)の要素がzero valueで初期化される。
  • len以内の要素の操作
    • slice[2]slice[1] = 2のように操作可能。
    • 代入は(当然ながら)破壊的。
  • lenを超える要素(というかインデックス)の操作
    • slice[5]のような操作をするとpanic。
    • 要素を追加するには、appendを使う。
    • append( slice, T... ) []T
    • appendは破壊的ではない。
    • `容量を超える量を追加しようとすると、自動的に容量を追加してくれる。
  • 再スライス可能。
    • 部分的に取り出して、別のスライスにする。
    • 配列部分はポインターなので、別のスライスになっても、同じメモリ領域をみている。
  • スライスのインデックスの取り出し方。
    • slice[ : ] -> すべての要素のスライス。
    • slice[ lo:hi ] ->lo番目からhi - 1`番目の要素のスライス。
    • slice[ lo: ] -> lo番目以降の要素のスライス。
    • slice[ :hi ] -> hi - 1番目までの要素のスライス。
  • 要素を削除する関数は用意されていない。

Array

以下の様な感じ。

func main() {
  ary1 := [3]string{ "dog", "cat" }
  fmt.Println( ary1 )
  fmt.Println( ary1[0] )
  fmt.Println( ary1[1] )
  fmt.Println( ary1[2] )

  ary2 := [3]int{ 10, 20 }
  fmt.Println( ary2 )
  fmt.Println( ary2[0] )
  fmt.Println( ary2[1] )
  fmt.Println( ary2[2] )

  // fmt.Println( ary2[3] )
}

//=>
// [dog cat ]
// dog
// cat
// 
// [10 20 0]
// 10
// 20
// 0

コメントアウトしている行を解くと、

invalid array index 3 (out of bounds for 3-element array)
 [process exited with non-zero status]

slice

基本的な操作を試してみる。

func main() {
  var sl0 []int
  fmt.Println( sl0 )
  fmt.Println( len( sl0 ) )
  fmt.Println( cap( sl0 ) )

  sl1 := make([]int, 5)
  fmt.Println( sl1 )
  fmt.Println( len( sl1 ) )
  fmt.Println( cap( sl1 ) )

  sl2 := make([]int, 5, 8 )
  fmt.Println( sl2 )
  fmt.Println( len( sl2 ) )
  fmt.Println( cap( sl2 ) )

  sl2[4] = 1

  fmt.Println( sl2 )
  fmt.Println( sl2[4] )
  // fmt.Println( sl2[5] )

  sl3 := append( sl2, 3, 4, 5, 6 )

  fmt.Println( sl2 )
  fmt.Println( len( sl2 ) )
  fmt.Println( cap( sl2 ) )

  fmt.Println( sl3 )
  fmt.Println( len( sl3 ) )
  fmt.Println( cap( sl3 ) )
}

//=>
// []
// 0
// 0
// [0 0 0 0 0]
// 5
// 5
// [0 0 0 0 0]
// 5
// 8
// [0 0 0 0 1]
// 1
// [0 0 0 0 1]
// 5
// 8
// [0 0 0 0 1 3 4 5 6]
// 9
// 16

コメントアウトしている行を解くと、

panic: runtime error: index out of range
 [process exited with non-zero status]

slicing slices

スライス:スライシング

[10 20 30 40 50 60 70 80 90]
[40 50 60 70]
[60 70 80 90]
[40 50 60]
[10 20 30 40 51 60 70 80 90]
[40 51 60 70]
[60 70 80 90]
[40 51 60]
[10 20 30 40 52 60 70 80 90]
[40 52 60 70]
[60 70 80 90]
[40 52 60]

Go言語:構造体とポインタ

Go言語:構造体 - sugilogのブログ のエントリーで、ポインタの部分の理解が曖昧だったので、復習。

参照したのは、こちら(M.Hiroi's Home Page / お気楽 Go 言語プログラミング入門)の記事

まずCのポインタのの利点

(1) 「参照呼び (call by reference) 」を実現する

(2) コンパクトで効率的なプログラムを書くことができる

Goでは、

  • Cとはできることが異なる。
  • ポインタについての整数値の代入によるアドレスの指定や演算はできない。
  • ポインタを利用した利点はそのままに、危険な操作ができないようになっている。

できることをまとめる

  • *のつく型は、その型のポインタ変数。
    • 初期値が指定されていない場合は、nilポインタに初期化される。(CのNULL相当)
  • &は、アドレス演算子。(Cの&と同様)
    • ポインタ変数の値を参照する場合は、変数のアクセスに*をつける。
    • ポインタ変数でない変数に*をつけてアクセスすると、エラーになる。
  • ポインタ変数に値を代入する場合も、変数のアクセスに*をつける。
    • ポインタに対して書き換えを行うので、同じポインタを参照する変数も書き換わる。
func main() {
  var n int  = 1
  var p *int = &n

  fmt.Println( n )
  fmt.Println( &n )
  fmt.Println( p )
  fmt.Println( *p ) 
}

//=>
// 1
// 0x10328000
// 0x10328000
// 1
func main() {
  var n int  = 1
  fmt.Println( *n )
}

//=>
// invalid indirect of n (type int)
//  [process exited with non-zero status]
func main() {
  var n int  = 1
  var p *int = &n

  fmt.Println( n )
  fmt.Println( &n )
  fmt.Println( p )
  fmt.Println( *p ) 

  *p = 2

  fmt.Println( n )
  fmt.Println( &n )
  fmt.Println( p )
  fmt.Println( *p ) 
}

//=>
// 1
// 0x10328000
// 0x10328000
// 1
// 2
// 0x10328000
// 0x10328000
// 2

構造体とポインタ

参照した記事では、配列で説明しているけど、今は構造体に置き換えていく。

アドレス演算子付きで構造体を初期化。(patternA)

  • 出力結果で、1つ目は&が付いているので、ポインタであることを示している。
  • 2つ目は、*をつけてアクセスしたので、値を示している。

ポインタを渡して、別の変数でフィールドの書き換えを行う。(patternB)

  • (話の流れ的に当たり前だけど)ポインタを渡した元の変数のフィールドも、同様にして書き換えられる。
    • 説明的には変数のフィールドが書き換わるというのはおかしいな。参照している先が変わったというべきか。
  • だってポインタだもの。

値を渡して、別の変数でフィールドの書き換えを行う。(patternC)

  • 値を渡した元の変数に変化はない。
  • だって、ポインタが違うもの。

new関数でゼロ初期化。(patternD、patternE)

  • アドレス演算子をつけて初期化したパターンと同じ挙動をする。
  • 値を代入するパターンも同様。

ポインタと構造体

Go言語:構造体

構造体(Struct)の話

  • structはフィールドの集まり。
    • フィールドへは、.(ドット)でアクセスする。
  • typeステートメントで定義する。
  • ポインタ演算はないが、ポインタを通した間接的なアクセスは可能。
  • 初期化のための値の指定は、ブレース{}内で指定する。
    • structのフィールドの定義順に、値を指定する。(部分的な無指定ができない。)
    • structのフィールド名をつけて、順不同に指定する。(一部分のみのアサイン可能。)
  • new関数を使うことで、0初期化して、ポインタを返す。
  • 構造体のフィールドで、0初期化しなかった場合の値のデフォルト値。
    • 文字列:""(から文字)
    • 数値:0(ゼロ)
    • 真偽値:false

試してみた

構造体