GoでWebアプリケーションを作る（4）ー関数とその定義方法

こんにちは。

今日は「関数とその定義方法」についてです。

関数の定義方法と、Goっぽい関数の扱われ方について、確認してもらいます。

# 関数の定義

Go言語では、`func`から始まる形で、関数を定義できます。フォーマットは以下の通りです。

```go
func <関数名>[[<型パラメータ1>,<型パラメータ2> ...]]([<引数1>, <引数2>...]) [(<戻り値1>, <戻り値2>...)] {
	<処理>
}
```

このフォーマットをみてわかる通り、Go言語では、複数の戻り値を指定できます。

なお、引数、戻り値がない場合は、省略可能なため、最も短い関数の定義は、以下のようになります。

```go
func myFunc() {
	<処理>
}
```

引数がいくつか存在し、戻り値が1つだけの場合は、以下のようになります。

```go
func myFunc(name string, age uint) bool {
	var find bool
	<処理>
	return find
}
```

また、戻り値が複数になると、戻り値の型をかっこで囲む必要があります。

```go
func myFunc(name string, age uint) (bool, error) {
	var find bool
	var result error
	<処理>
	return find, result
}
```

関数の基本的な定義方法は以上です。

## 変数名を戻り値の定義で、合わせて定義する方法もあります。

変数の名前スコープが、関数内全体のスコープになり、認識すべき範囲が広がるため、執筆者は、あまり扱いません。

```go
func myFunc(name string, age uint) (find bool, result error) {
	<処理>
	return find, result
}
```
## 型パラメータの使用 (Generics)

`interface`型を用いることで任意の型を受け付けられる関数を作成できます。 便利なようですが、実行時に型を解決するため意図しない挙動を取る可能性があります。 Go1.18でGenericsが導入されコンパイル時に型を解決することが可能になりました。 興味のあるひとは[Genericsのチュートリアル](https://go.dev/doc/tutorial/generics)をやってみてください。

```go
type Number interface {
	int64 | float64
}
func myFunc[T any,N Number](hoge []T, fuga N) []T {
	<処理>
	return res
}
```

# 関数を書いてみる

Part3で、修正したソースコードを更新してもらいます。
作成する関数は、渡された牛丼名を食べる関数です。
本講義で行えることは限られるので、行為を以下のように定義します。

- 空白以外の文字列を全て牛丼名とみなす
- 食べる行為は、標準出力とする
    - 本章では、`fmt.Println` を用いて、文字列を出力します
- 食べた場合は、`true`, 食べたい無い場合は、`false`を返す

### if構文

本章のハンズオンでは、上述した定義を表すために、条件分岐が必要となります。
条件分岐を表すための、Go言語によるif構文の書き方を先に紹介します。

基本的なif構文は、以下の通りです。
本講義では割愛しますが、`else if`や、`else`を続ける表現もあります。

```go
if <条件> {
	<true処理>...
}
```
具体例は、以下のような表現となります。

```go
func main() {
	var val1 string = "a"
	var val2 string = "a"
	if val1 == val2 {
		fmt.Println("True!")
	}
}
```
この例では、`val1 == val2` が条件式となり、bool値（`True` or `False`) を返却。それを元にifで条件分岐する流れを表しています。

この他に、Go言語では、ifの中で関数の戻り値を変数に代入し、変数を条件の要素とする書き方もあります。
構文は以下のようになります。

```go
if <判定要変数>... := <関数>(); <条件> {
	<判定式 true処理>...
}
```

具体例は、以下のような表現となります。

```go
func myTest() bool {
#...省略

func main() {
	if res := myTest(); !res {
		fmt.Println("False")
	}
}
```
`myTest()`が、bool値`(True or False)` を返却し、resへ代入します。そして、`;`の後に続く条件式(`!res`)の結果に応じて条件分岐します。

### 関数のコーディングを行う
```
:# TERMINAL 0

$ cd /go/src/go_tutorial/4_funcy/monkey/
$ <お好きなエディタ> eaters.go
$ go run eaters.go
```

- `/go/src/go_tutorial/4_funcy/monkey/eaters.go`

```go
package main

import "fmt"

func Eat(name string) bool {
	<nameが空白か比較する>
	<nameが空白ならば> {
		<`return false`を行う>
	 }
	<nameが空白以外ならば、`fmt.Println(name)`を実行し、`return true`を行う>
}

func main() {
	var name1 string = "GYUDON"
	if ok := Eat(name1); !ok {
		fmt.Println("cannt eat: ", name1)
	}

	var name2 string = ""
	if ok := Eat(name2); !ok {
		fmt.Println("cannt eat: ", name2)
	}
}
```

#### 結果

```
:# TERMINAL 0

$ go run eaters.go
GYUDON
cannt eat: 
```
さて、既にお気づきの方もいると思いますが、`if ok := Eat(name1); !ok {` などの表現は、冗長です。

変数に代入せず、`if !Eat(name1) {` というような表現であっても動作します。
こちらの表現の方が、シンプルです。可読性や処理の観点からも良さそうです。

しかし、Goっぽい関数を扱う場合に、シンプルな表現は、活用できないケースがあります。

### Goっぽい関数の書き方と、使い方

Go言語の関数は、戻り値を複数返せる特徴を持っています。

この特徴を活用した、よく使われる表現として、関数の実行内容と、処理結果それぞれを戻り値で返却するというものが挙げられます。
例えば、以下のような関数です。

```go
func Writer() (int, bool) {
#...省略
```

この例では、第一戻り値に、書き込んだ文字数を返し、
第二戻り値に、問題なく書き込めたかどうかを判定するbool値を返却しています。

この関数`Writer()`は、複数の戻り値を持つため、ifに、bool値だけを渡すことができません。
その為、この関数のbool値だけを用いて条件分岐を行う場合には、
第一戻り値を捨て、第二戻り値だけを変数へ代入するというような記法が必要となります。

```go
if _, ok := Writer(); !ok {
	fmt.Println("cannot write")
}
```

（任意の戻り値を捨てるには、`_`へ代入します。全て捨てる場合は、代入自体が不要です。）

今回は、説明の例として、boolによる成功可否の判定を挙げましたが、
Go言語では、エラー状態を格納できるerror型を用いることが多くあります。

なお、error型は、ifがbool型を渡されたように判定することはできません。
そのため、error型の空の状態（`nil`）をエラーではない状態として、それと比較することで条件分岐を行う書き方が多く扱われています。

```go
func Writer() (string, error) {
#...省略
if _, err := Writer(); err != nil {
	fmt.Println("cannot write")
}
```

#### Goっぽい関数を実行してみる


```
:# TERMINAL 0

$ cp /../monkey/eaters.go /../likego/eaters.go
$ cd /../likego/
$ <お好きなエディタ> eaters.go
$ go run eaters.go
```

- /.../likego/eaters.go

```go
package main

import "fmt"

func Eat(name string) (bool, error) {
	if name == "" {
		return false, fmt.Errorf("name is empty.")
	}
	fmt.Println(name)
	return true, nil
}

func main() {
	var name1 string = "GYUDON"
	if _, err := Eat(name1); err != nil {
		fmt.Println("cannot eat: ", err)
	}

	var name2 string = ""
	if _, err := Eat(name2); err != nil {
		fmt.Println("cannot eat: ", err)
	}
}
```

#### 結果

```
:# TERMINAL 0

$ go run eaters.go
GYUDON
cannot eat: name is empty.
```
##### 変数スコープの注意事項

戻り値の変数を後ほど活用する場合は、スコープに注意してください。
例えば、ファイル読み込みなどで、(`読み込んだ文字列`, `error`) のペアが戻り値となるケースです。
例1では、第一戻り値が、ifのスコープ内となり、後続処理で活用できません。
例2のように、ifの手前で一度変数を定義する必要があります。

- 例1

```go
func FileReader(file_path string) (string, error) {
#...省略

func main() {
	var fpath string = "c:\mydata\data.txt"
	if body, err := FileReader(fpath); err != nil {
		panic(err)
	}
	// ここでは body がスコープ外
}
```

- 例2

```go
func FileReader(file_path string) (string, error) {
#...省略

func main() {
	var fpath string = "c:\mydata\data.txt"
	body, err := FileReader(fpath)
	if err != nil {
		panic(err)
	}
	// body がスコープ内（で、扱える）
```

# 関数の実行される順番

Go言語では、`func main(){}` が、実行されます。
そのため、仮に以下のようなソースコードを実行すると、`runtime.main_main·f: function main is undeclared in the main package` と、main関数が見つからないエラーが表示されます。

```go
package main

import "fmt"

func MySuperFunction() {
	var value string = "GYUDON"
	fmt.Println(value)
}
```

## init関数

`func main(){}` よりも先に実行される、`func init(){}` という関数があります。
後ほど触れるパッケージで、初期化処理を行いたいケースなどで活用できます。

今日は以上です。
ありがとうございました。

次のPart 5は「パッケージを使う」です。
よろしくお願いいたします。