トラック
/
Go
Go
/
演習
/
ラザニアの達人
ラザニアの達人

ラザニアの達人

学習演習

はじめに

関数を使うと、コードを再利用できるひとつのまとまりにグループ化できます。 関数は、funcキーワード、関数名、そして括弧(())の中にカンマで区切って並べた0個以上の仮引数と型からなります。

関数の仮引数

仮引数はすべて明示的に型を指定する必要があり、仮引数の型推論はありません。 仮引数にデフォルト値はないため、関数の仮引数はすべて必須です。

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// Two parameters
func PrintGreetingName(greeting string, name string) {
  fmt.Println(greeting + " " + name)
}

同じ型の仮引数はまとめて宣言でき、そのあとに型を一度だけ書きます。

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

仮引数と実引数

よく混同されやすい2つの用語、parametersとargumentsについて、簡単に見ておきましょう。 関数の仮引数とは、関数のシグネチャで定義される名前のことです。たとえば、上の関数PrintGreetingNameではgreetingとnameがそれにあたります。 関数の実引数とは、関数を呼び出すときに仮引数へ渡す具体的な値のことです。 たとえば、次の例では、"Hello"と"Katrina"が、greetingとnameという仮引数に渡される実引数です。

PrintGreetingName("Hello", "Katrina")

戻り値

関数の仮引数のあとには、0個以上の戻り値が続き、これらも明示的に型を指定する必要があります。 戻り値が1つのときはそのまま書き、複数あるときは括弧でくくります。 値は、returnキーワードを使って関数から呼び出し元のコードへ返されます。 1つの関数の中に複数のreturn文を書くこともできます。 関数の実行は、それらのreturn文のいずれかに到達した時点で終わります。 関数から複数の値を返す場合は、カンマで区切ります。

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

関数の呼び出し

関数を呼び出すには、関数名を指定し、その関数の仮引数それぞれに対応する実引数を括弧の中に渡します。

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

名前付き戻り値とnaked return

仮引数と同様に、戻り値にも名前を付けることができます。 名前付き戻り値を使うと、引数なしのreturn文がそれらの値を返します。 これはnaked returnと呼ばれます。

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

値渡しと参照渡し

値渡しと参照渡しという概念も、ここで整理しておきましょう。

まずは値渡しから見てみましょう。 次の例では、変数valを関数MultiplyByTwoに渡すとき、valのコピーを渡しています。 そのため、newValは更新後の値4を持ちますが、元の変数valは依然として2のままです。 内部的には、Goは元の値のコピーを作り、関数によって変更されるのはこのコピー(つまりv)だけです。

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

厳密に言えば、Goではすべての実引数は値渡しです。つまり、関数に渡された値やデータのコピーが作られます。 しかし、関数に渡すデータのコピーを作らずに、関数の中でそのデータを変更したい場合は、pointersを実引数として使います。これが参照渡しです。

ポインタ

参照渡しを実現するには、pointerを使います。 pointerを実引数として関数に渡すことで、データのコピーを操作するだけでなく、関数に渡された元のデータそのものを変更できます。

今のところは、ポインタ型は関数のシグネチャで型の前に付いた*で見分けられる、と知っておけば十分です。

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

pointerという概念がよくわからなくても、心配いりません。 シラバスの後半に、ポインタをマスターするための専用のセクションを用意しています。

例外

slicesとmapsは、上述のルールの例外であることに注意してください。 sliceやmapを実引数として関数に渡すとき、型に明示的な*がなくても、これらはポインタ型として扱われます。 つまり、スライスやマップを関数に渡してその元のデータを変更すると、その変更は元のスライスやマップにも反映されます。

説明

この演習では、お気に入りの料理本に載っている絶品ラザニアの準備と調理に関するコードをもう少し書いていきます。

4つのタスクがあります。 最初のタスクは調理そのものに関係し、残りの3つは完璧な準備に関するものです。

1. 準備時間を見積もる

次に準備するラザニアのために、調理を楽しめるように、十分な時間を確保しておきたいですね。 ラザニアにどの層を入れるかはもう決めてあります。 そこで、それをもとに準備にどれくらい時間がかかるかを見積もりたくなります。

層のスライスを[]stringとして、1層あたりの平均準備時間(分)をintとして受け取る関数PreparationTimeを実装します。 この関数は、層の数に基づく総準備時間の見積もりをintとして返します。 Goの関数にはデフォルト値がありません。 平均準備時間として0(intのデフォルトの初期値)が渡された場合は、デフォルト値の2を使います。

layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}
PreparationTime(layers, 3)
// => 18
PreparationTime(layers, 0)
// => 12

2. 必要な麺とソースの量を計算する

時間を確保するだけでなく、夢のラザニアを作るのに十分なソースと麺があることも確認したいですね。 ラザニアの麺の層ごとに、50グラムの麺が必要です。 ソースの層ごとに、0.2リットルのソースが必要です。

層のスライスを[]stringの入力として受け取る関数Quantitiesを定義します。 この関数は、食事を作るのに必要な麺とソースの量を求めます。 結果は、noodlesをint、sauceをfloat64とした2つの値として返します。

Quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})
// => 100, 0.4

3. 秘密の材料を加える

少し前、友人の家を訪ねてラザニアを食べました。 それは素晴らしく、何か特別なものがありました。 友人は材料のリストを送ってくれて、リストの最後の項目が、その料理を特別にした「秘密の材料」だと教えてくれました。 今度はその秘密の材料を自分のレシピにも加えたくなります。

[]string型の材料のスライスを2つ、入力として受け取る関数AddSecretIngredientを書きます。 1つ目の入力は友人が送ってくれたリストで、2つ目は自分のレシピの材料リストです。 自分の材料リストの最後の要素は常に"?"です。 この関数は、それを友人のリストの最後の項目で置き換えます。 注意:AddSecretIngredientは何も返しません。自分の材料リストを直接変更します。 友人の材料のリストは変更してはいけません。 また、sliceはポインターとして関数に渡されるため、AddSecretIngredientに渡された2つの[]string引数への変更は直接反映されます。

friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"}
myList := []string{"noodles", "meat", "sauce", "mozzarella","?"}

AddSecretIngredient(friendsList, myList)
// myList => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}

4. レシピの分量を調整する

料理本に載っている分量では、ラザニアは2人分しかできません。 次はもっと多くの人のために料理したいので、人数に応じた分量を計算したくなります。

2つの入力を受け取る関数ScaleRecipeを実装します。

  • 2人分に必要なfloat64の分量のスライス。
  • 作りたい人数分。

この関数は、希望する人数分に必要な分量をfloat64のスライスとして返します。 ただし、元のレシピはそのままにしておきたいです。 つまり、この関数ではquantities引数を変更してはいけません。

quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
Go Exercism

ラザニアの達人を始める準備はできましたか?

Exercismに登録すれば、34個のコンセプト166個の演習、そして本物の人間によるメンタリングとともに、Goを学んでマスターできます。すべて無料です。