step9 再帰関数によるループ処理
このステップでは、これまで while 式で表現していたループ処理を、再帰関数を使って書き直す。
F# における再帰処理の基本的な考え方を理解し、再帰を使った関数型プログラミングのスタイルを身につけよう。
再帰関数とは
再帰関数とは、自分自身の処理の中で自分自身を呼び出す関数のことである。
再帰関数を使うと、while 式などのループ構文を使わずに、繰り返し処理を表現できる。
例えば、リストの要素の合計を計算する関数 sum は、再帰的に定義できる。
let rec sum list = match list with | [] -> 0 | head :: tail -> head + sum tailsum 関数は、与えられたリストが空リスト [] の場合は 0 を返し、それ以外の場合は先頭要素 head と残りのリスト tail の合計を返す。
残りのリストの合計を計算するために、sum 関数自身を呼び出していることがわかる。
リストの :: 演算子は、リストの先頭に要素を付加した新しいリストを作成する。
再帰関数を定義する際は、let ではなく let rec を使う必要がある。
rec キーワードは、関数が再帰的であることをコンパイラに伝える役割がある。
ループ処理を再帰関数で書き直す
数当てゲームのループ処理を、再帰関数を使って書き直してみよう。
まず、ループの中心となる処理を gameLoop という名前の関数として定義する。
let rec gameLoop answer = printfn "数字を入力してください:" let inputString = stdin.ReadLine() let inputNumber = int inputString
let result = judge answer inputNumber
match result with | TooBig -> printfn "大きすぎます!" gameLoop answer | TooSmall -> printfn "小さすぎます!" gameLoop answer | Correct -> printfn "正解!"gameLoop 関数は、正解の数 answer を引数として受け取る。
関数内では、まずユーザーに数字の入力を促す。
次に、入力された数字を judge 関数を使って判定し、結果に応じてメッセージを表示する。
結果が Correct でない場合、gameLoop 関数自身を answer を引数として再度呼び出す。
これにより、ユーザーが正解するまで処理が繰り返される。
gameLoop 関数を定義したら、Program.fs の while ループの部分を、gameLoop 関数の呼び出しに置き換える。
let random = Random()let answer = random.Next(1, 101)printfn "正解は%dです" answer
gameLoop answergameLoop 関数は answer を引数として受け取るので、answer を渡している。
Program.fs の全体像
ここまでの変更を適用した Program.fs の全体像は以下のようになる。
open System
type GameResult = | TooBig | TooSmall | Correct
let judge answer input = match compare input answer with | 1 -> TooBig | -1 -> TooSmall | _ -> Correct
let rec gameLoop answer = printfn "1から100までの数字を入力してください。:" let inputNumber = stdin.ReadLine() |> int let result = judge answer inputNumber
match result with | TooBig -> printfn "大きすぎます!" gameLoop answer | TooSmall -> printfn "小さすぎます!" gameLoop answer | Correct -> printfn "正解!"
let answer = Random().Next(1, 101)printfn "正解は%dです" answer
gameLoop answer書き換えたら dotnet run で実行してみよう。
while ループを使った場合と同じように、ユーザーが正解するまで繰り返し入力を促されるはずだ。
再帰関数の注意点
再帰関数は、ループ処理を表現する強力な方法だが、注意すべき点もある。 再帰呼び出しが深くなりすぎると、スタックオーバーフローエラーが発生する可能性がある。
例えば、以下の infiniteRecursion 関数は、永遠に自分自身を呼び出し続けるため、最終的にスタックオーバーフローエラーとなる。
let rec infiniteRecursion () = infiniteRecursion ()
infiniteRecursion () // スタックオーバーフローエラーが発生再帰関数を設計する際は、必ず停止条件を設けることが重要だ。
gameLoop 関数の場合、result が Correct になったときに再帰呼び出しが停止するようになっている。
まとめ
このステップでは、以下の内容を学んだ。
- 再帰関数の定義方法
let recキーワードの使用- 再帰関数を使ったループ処理の表現
gameLoop関数の作成とwhileループの置き換え- 再帰関数の注意点と末尾再帰
- リストを使った再帰関数の例 (
sum関数)
再帰関数を使うことで、while ループを使わずに繰り返し処理を表現できた。
F# では、再帰はループを表現する一般的な方法であり、関数型プログラミングの重要な要素である。
再帰関数を使いこなすことで、より F# らしいコードを書くことができるようになるだろう。
このステップで、数当てゲームは一通り完成した。 次のステップでは、ゲームのルール説明を追加するなどして、ユーザビリティを向上させよう。