汎用関数に型を付けよ
中段 / 目安 30分
凛さんがリポジトリの検索結果を見せました。同じ形の関数が3つ並んでいます。firstItem、firstUser、firstNotice。中身は全部 arr[0] を返しているだけで、違うのは型注釈だけでした。
「一覧の先頭を取りたいのは、商品だけではありません。ユーザー一覧でも通知一覧でも同じことをします。中身が同じなのに3つあるのは、3箇所に直す義務があるということです」
では1つにまとめるとして、引数の型は何でしょうか。any[] にすれば全部通ります。しかし通った先で、戻り値も any になります。first(users)[0].nmae と綴りを間違えても、コンパイラは何も言いません。凛さんの一言は短いものでした。「anyは論外です」
ここで使うのが ジェネリクス です。関数名のうしろに <T> と書くと、T は「呼ばれるときに決まる型」を表す入れ物になります。first<T>(arr: T[]): T | undefined と書けば、商品の配列を渡したときは T が Item になり、戻り値は Item | undefined になります。文字列の配列を渡せば string | undefined です。呼び出しごとに型が付け替わるわけです。
呼ぶ側で <Item> と明示する必要はありません。渡した配列から TypeScript が推論します。ここが any との決定的な差です。any は型を捨てますが、ジェネリクスは型を 預かって、そのまま返します。
戻り値に | undefined が付いているのは、空の配列を渡されることがあるからです。前の問で扱った絞り込みが、そのまま呼び出し側の責任になります。凛さん曰く「汎用にするというのは、誰が使っても壊れないようにするということです。空配列は必ず来ます」
完成条件
次の2つを定義してください。
first<T>(arr: T[]): T | undefined— 配列の先頭を返す。空ならundefinedを返すsummarize(items, users)—firstを 2つの違う型で呼んで、結果をまとめた文字列を返す
items は商品の配列です。商品は name (string) と price (number) を持ちます。users は名前の配列で、こちらは string[] です。
summarize が返す文字列は次の形です。
プレーンテキスト
商品 深煎りブレンド 480円 / 会員 田中はじめそれぞれ先頭が無いときの表記は次のとおりです。
- 商品が空のとき —
商品 なし - 会員が空のとき —
会員 なし
両方空なら 商品 なし / 会員 なし になります。区切りは半角スペースを挟んだ / です。
any は使わないでください。first の中で arr[0] をそのまま返すと、strict でも T として扱われてしまう点に注意してください。長さを確かめてから返すか、T | undefined を明示した受け取り方をしてください。どちらでも構いませんが、空配列で undefined が返ることを必ず確かめてください。
進め方
白紙から書いてください。手が止まったらヒントを開けます。ヒントは3段階で、①方針、②使う構文、③部分解の順に出ます。1つも開けずに通せたら、その問題は自分のものです。
開かずに10分粘って進まないなら、①だけ開けてください。粘る時間そのものには価値がありません。価値があるのは、自分で書いた行が動いた瞬間です。
要件
- first
(arr: T[]): T | undefined を定義する。型引数を使い、any は使わない - 空の配列を渡したら undefined が返ること
- interface Item を定義する。持つのは name(string) と price(number)
- summarize(items: Item[], users: string[]): string を定義する
- summarize は first を Item[] と string[] の2つの型で呼ぶこと
- 戻り値は「商品 深煎りブレンド 480円 / 会員 田中はじめ」の形
- 先頭が取れなかった側は「商品 なし」「会員 なし」と書く
入出力例
summarize([{"name":"深煎りブレンド","price":480},{"name":"抹茶ラテ","price":580}], ["田中はじめ","佐藤みどり"]) → "商品 深煎りブレンド 480円 / 会員 田中はじめ"
summarize([], ["佐藤みどり"]) → "商品 なし / 会員 佐藤みどり"
summarize([{"name":"チーズケーキ","price":480}], []) → "商品 チーズケーキ 480円 / 会員 なし"
summarize([], []) → "商品 なし / 会員 なし"
summarize([{"name":"季節のフルーツティー","price":620},{"name":"自家製レモネード","price":560},{"name":"カフェオレ","price":520}], ["山口あゆみ","中村けいた","小林ひかる"]) → "商品 季節のフルーツティー 620円 / 会員 山口あゆみ"ヒント
前のヒントを開くと次が開きます。開かずに解けると未開封クリアです