こんにちは。よっしーです(^^)
今日は、Golangでの公開済みfunctionにおけるパラメータ変更についてご紹介します。
前提条件
前回の記事では、ランダム処理についてご紹介しました。
今回の記事では、1つのリクエストで複数の人の挨拶を得るためのサポートを追加します。つまり、複数の値の入力を処理し、その入力値を複数の値の出力とペアにするのです。そのためには、挨拶文を返す関数に、名前の集合を渡す必要があります。
下記の記事が実行できる環境にあることを前提にしています。
利用方法
作業ディレクトリを作成して、移動する。
mkdir 09_learn-golang-change-parameter
cd 09_learn-golang-change-parameter
asdfコマンドで使用するGolangのバージョンを指定します。
すでにGolangが実行できる状態にある方は、このコマンドをスキップしても問題ありません。
asdf local golang 1.20.5
前回の記事のソースコードを作業ディレクトリにコピーします。
cp -r ../08_learn-golang-handle-random/greetings .
cp -r ../08_learn-golang-handle-random/hello .
以下のコードをgreetings.goファイルに貼り付け、ファイルを保存してください。
挨拶文を返すHello関数に、名前の集合を渡す必要があるのですが、不都合な点があります。Hello関数のパラメータを単一の名前から一連の名前に変更すると、関数のシグネチャが変更されます。もしあなたがexample.com/greetingsモジュールをすでに公開しており、ユーザーがすでにHelloを呼び出すコードを書いていた場合、この変更によって彼らのプログラムは壊れてしまうでしょう。
このような状況では、別の名前で新しい関数を書くのがベターな選択です。新しい関数は、複数のパラメータを受け取ります。これにより、後方互換性のために古い関数が保存されます。
package greetings
import (
"errors"
"fmt"
"math/rand"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
// If no name was given, return an error with a message.
if name == "" {
return name, errors.New("empty name")
}
// Create a message using a random format.
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
// Hellos returns a map that associates each of the named people
// with a greeting message.
func Hellos(names []string) (map[string]string, error) {
// A map to associate names with messages.
messages := make(map[string]string)
// Loop through the received slice of names, calling
// the Hello function to get a message for each name.
for _, name := range names {
message, err := Hello(name)
if err != nil {
return nil, err
}
// In the map, associate the retrieved message with
// the name.
messages[name] = message
}
return messages, nil
}
// randomFormat returns one of a set of greeting messages. The returned
// message is selected at random.
func randomFormat() string {
// A slice of message formats.
formats := []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
// Return one of the message formats selected at random.
return formats[rand.Intn(len(formats))]
}
以下のコードをhello.goファイルに貼り付け、ファイルを保存してください。
前回との差分は、Hellos関数の呼び出しコードで、名前のスライスを渡し、戻ってきた名前/メッセージマップの中身を表示します。
package main
import (
"fmt"
"log"
"example.com/greetings"
)
func main() {
// Set properties of the predefined Logger, including
// the log entry prefix and a flag to disable printing
// the time, source file, and line number.
log.SetPrefix("greetings: ")
log.SetFlags(0)
// A slice of names.
names := []string{"Gladys", "Samantha", "Darrin"}
// Request greeting messages for the names.
messages, err := greetings.Hellos(names)
if err != nil {
log.Fatal(err)
}
// If no error was returned, print the returned map of
// messages to the console.
fmt.Println(messages)
}
helloディレクトリに移動して、hello.goを実行し、コードが動作することを確認します。
cd hello
go run .
下記のようなメッセージが確認できればOKです。
% go run .
map[Darrin:Great to see you, Darrin! Gladys:Great to see you, Gladys! Samantha:Great to see you, Samantha!]
解説
greetings.go
package greetings
import (
"errors"
"fmt"
"math/rand"
)
// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
// If no name was given, return an error with a message.
if name == "" {
return name, errors.New("empty name")
}
// Create a message using a random format.
message := fmt.Sprintf(randomFormat(), name)
return message, nil
}
// Hellos returns a map that associates each of the named people
// with a greeting message.
func Hellos(names []string) (map[string]string, error) {
// A map to associate names with messages.
messages := make(map[string]string)
// Loop through the received slice of names, calling
// the Hello function to get a message for each name.
for _, name := range names {
message, err := Hello(name)
if err != nil {
return nil, err
}
// In the map, associate the retrieved message with
// the name.
messages[name] = message
}
return messages, nil
}
// randomFormat returns one of a set of greeting messages. The returned
// message is selected at random.
func randomFormat() string {
// A slice of message formats.
formats := []string{
"Hi, %v. Welcome!",
"Great to see you, %v!",
"Hail, %v! Well met!",
}
// Return one of the message formats selected at random.
return formats[rand.Intn(len(formats))]
}
このコードは、greetings
というパッケージ内に定義された関数とメソッドを含んでいます。以下にコードの説明を示します。
import
ステートメント:errors
、fmt
、math/rand
の3つのパッケージをインポートしています。Hello
関数:name
という文字列を引数として受け取ります。関数は文字列とエラーを返します。処理の概要は以下の通りです。name
が空の場合、エラーを返します(errors.New("empty name")
)。- ランダムなフォーマットを使ってメッセージを作成します。
- フォーマット文字列に
name
を埋め込んでメッセージを生成し、それを返します。
Hellos
関数:names
という文字列のスライスを引数として受け取ります。関数はマップとエラーを返します。処理の概要は以下の通りです。- 名前とメッセージを関連付けるためのマップ
messages
を作成します。 - 受け取った名前のスライスをループし、各名前に対して
Hello
関数を呼び出してメッセージを取得します。 - エラーが発生した場合は、
nil
とエラーを返します。 - マップ
messages
に取得したメッセージを名前と関連付けて格納します。 - 最終的に、名前とメッセージが関連付けられたマップ
messages
とnil
を返します。
- 名前とメッセージを関連付けるためのマップ
randomFormat
関数: ランダムに選択された挨拶メッセージを返します。以下の手順で処理が行われます。formats
という文字列のスライスに複数のフォーマットが定義されています。rand.Intn(len(formats))
を使ってformats
スライスの中からランダムにインデックスを選択し、そのインデックスに対応するメッセージフォーマットを返します。
このコードは、Hello
関数を呼び出して指定した名前に対してランダムな挨拶メッセージを生成することができます。また、Hellos
関数を呼び出すことで複数の名前に対して一括で挨拶メッセージを生成し、名前とメッセージが関連付けられたマップを取得することができます。
mapについて
この記事のHellos関数では、指定された各人物を挨拶メッセージに関連付けたmapを返すように実装しています。
Go言語のマップ(map)は、キーと値のペアを保持する連想配列です。マップはデータの格納や検索に使用され、効率的なデータ管理が可能です。以下にマップについての詳細を説明します。
- マップの作成: マップは
make
関数を使用して作成します。マップの型はmap[keyType]valueType
と指定します。
// string型のキーとint型の値を持つマップの作成
m := make(map[string]int)
- マップへの要素の追加と更新: マップに要素を追加するには、キーを指定して値を代入します。キーが存在しない場合は新しい要素が追加され、キーが既に存在する場合は値が更新されます。
m := make(map[string]int)
m["apple"] = 1
m["banana"] = 2
- マップからの要素の削除:
delete
関数を使用してマップから要素を削除します。
m := make(map[string]int)
m["apple"] = 1
delete(m, "apple")
- マップの要素へのアクセス: マップの要素にアクセスするには、キーを指定して対応する値を取得します。キーが存在しない場合はゼロ値が返されます。
m := make(map[string]int)
m["apple"] = 1
value := m["apple"] // 1
- マップの要素の存在チェック: マップに特定のキーが存在するかどうかを確認するには、2つの変数を使用して、存在チェックと値の取得を行います。
m := make(map[string]int)
m["apple"] = 1
value, exists := m["apple"]
if exists {
// キーが存在する場合の処理
}
- マップの長さ: マップの要素数(キーの数)は
len
関数を使用して取得できます。
m := make(map[string]int)
m["apple"] = 1
length := len(m) // 1
- イテレーション:
for range
構文を使用してマップの要素に順番にアクセスすることができます。
m := make(map[string]int)
m["apple"] = 1
m["banana"] = 2
for key, value := range m {
fmt.Println(key, value)
}
- マップの順序: マップは要素の格納順序を保持しません。要素の順序は保証されず、取得した順序が常に同じであることは保証されません。
- マップのゼロ値: マップは参照型であり、ゼロ値は
nil
です。ゼロ値のマップは要素数0で、メモリ上には割り当てられていません。
var m map[string]int // nilマップ
fmt.Println(m == nil) // true
- マップの宣言と初期化: マップの宣言と同時に初期化する場合、リテラル構文を使用して初期値を指定できます。
// string型のキーとint型の値を持つマップの宣言と初期化
m := map[string]int{
"apple": 1,
"banana": 2,
}
マップはデータを効率的に管理するための便利なデータ構造であり、キーと値のペアを関連付けるために使用されます。プログラムの中でデータの集合を表現する必要がある場合や、高速なデータの検索が必要な場合に特に有用です。
おわりに
この記事では、名前と値のペアを表現するためのマップについて紹介しました。また、モジュールの新機能や変更機能に対して新しい関数を実装することで、後方互換性を保つという考え方も紹介しました。後方互換性の詳細については、「モジュールの互換性を保つ」を参照してください。
本記事で使用したコードは下記のリポジトリにあります。
何か質問や相談があれば、遠慮なくコメントしてください。また、エンジニア案件についても、いつでも相談にのっていますので、お気軽にお問い合わせください。
それでは、また明日お会いしましょう(^^)
コメント