step10 Option型とtryParseによるエラーハンドリング
ここまでのステップで、数当てゲームは一通り完成した。しかし、現状のコードにはまだ改善の余地がある。このステップでは、F# におけるエラーハンドリングの方法として、Option 型と tryParse 関数について学び、ユーザーが数字以外の文字列を入力した場合でも、プログラムがクラッシュしないように改良する。また、より明示的なエラー表現としてGameResult型にNaNを追加する。
Option 型
F# の Option 型は、値が存在するかどうかを表現するための型である。Option 型は、Some と None の 2 つの判別子を持つ判別共用体として定義されている。
type Option<'T> = | Some of 'T | NoneSome は値が存在する場合を表し、None は値が存在しない場合を表す。例えば、整数値を返す可能性のある関数は、int option 型の値を返すように定義できる。
let tryParseInt (input: string): int option = // ...tryParseInt 関数は、文字列 input を整数に変換しようと試みる。変換に成功した場合は Some で包まれた整数値を返し、失敗した場合は None を返す。
Int32.TryParse メソッド
F# では、Int32.TryParse メソッドを使って、文字列を整数に変換できる。このメソッドは、変換に成功したかどうかを表す bool 値と、変換後の整数値を out パラメータで返す。
let success, result = Int32.TryParse("123")// success = true, result = 123
let success, result = Int32.TryParse("abc")// success = false, result = 0 (out パラメータの値は不定)Int32.TryParse メソッドを直接使うこともできるが、F# では out パラメータの使用は一般的ではない。代わりに、Int32.TryParse をラップして、int option 型の値を返す tryParseInt 関数を定義してみよう。
let tryParseInt (input: string): int option = let success, result = Int32.TryParse(input) if success then Some result else Noneこの tryParseInt 関数は、Int32.TryParse メソッドを呼び出し、変換に成功した場合は Some で包まれた整数値を返し、失敗した場合は None を返す。
Option 型と match 式
Option 型の値を使用する際は、match 式を使ってパターンマッチングを行うのが一般的である。
let parsedInt = tryParseInt "123"
match parsedInt with| Some i -> printfn "変換に成功: %d" i| None -> printfn "変換に失敗"parsedInt の値が Some の場合は、整数値が i に束縛されて処理が実行される。None の場合は、変換に失敗したときの処理が実行される。
空白文字の考慮
ユーザーの入力には、数字の前後に余計な空白文字が含まれている可能性がある。これらに対応するため、gameLoop 関数内で inputString に対して Trim() を適用する。
let rec gameLoop answer = printfn "1から100までの数字を入力してください。:" let inputString = stdin.ReadLine().Trim() // ...inputString.Trim() とすると、inputString の値が変更不能なためエラーになる。上記のように、inputString に stdin.ReadLine().Trim() の結果を束縛する。
GameResult 型に NaN を追加
これまで数字以外の入力があった場合にはエラーメッセージを表示し、gameLoopを再帰呼び出ししていた。GameResult型を使うと、この状況をより洗練された型で表現できる。そこで、GameResult型にNaNを追加し、数字以外の入力があったことを表すようにする。
type GameResult = | TooBig | TooSmall | Correct | NaNGameResult型にNaNを追加することで、数値以外の入力があったことを明示的に表せるようになる。
match 式を使った判定
match式を使ってよりシンプルにエラーハンドリングを記述する。
let rec gameLoop answer = printfn "1から100までの数字を入力してください。:" let inputString = stdin.ReadLine().Trim() |> tryParseInt let result = match inputString with | Some inputNumber -> judge answer inputNumber | None -> NaN
let message = match result with | TooBig -> "大きすぎます!" | TooSmall -> "小さすぎます!" | Correct -> "正解!" | NaN -> "無効な入力です。数字を入力してください。"
printfn "%s" message
if result <> Correct then gameLoop answerここでは、inputString を tryParseInt 関数で整数に変換し、その結果を直接match式で評価している。
tryParseIntの結果がSome inputNumberの場合は、judge answer inputNumberの結果(GameResult 型)をresultに束縛する。tryParseIntの結果がNoneの場合は、resultにNaNを束縛する。
そして、resultの値に応じてメッセージを作成し、表示している。result が Correct でない場合は、gameLoop 関数を再帰的に呼び出し、ゲームを続行する。
Program.fs の全体像
それでは、Program.fs の全体像を見てみよう。
open System
type GameResult = | TooBig | TooSmall | Correct | NaN
let tryParseInt (input: string): int option = let success, result = Int32.TryParse(input) if success then Some result else None
let judge answer input = match compare input answer with | 1 -> TooBig | -1 -> TooSmall | _ -> Correct
let rec gameLoop answer = printfn "1から100までの数字を入力してください。:" let inputString = stdin.ReadLine().Trim() |> tryParseInt
let result = match inputString with | Some inputNumber -> judge answer inputNumber | None -> NaN
let message = match result with | TooBig -> "大きすぎます!" | TooSmall -> "小さすぎます!" | Correct -> "正解!" | NaN -> "無効な入力です。数字を入力してください。"
printfn "%s" message
if result <> Correct then gameLoop answer
let answer = Random().Next(1, 101)printfn "正解は%dです" answer // デバッグ用に正解を表示
gameLoop answerまとめ
このステップでは、以下の内容を学んだ。
- Option 型を使ったエラーハンドリング
tryParseInt関数の定義Int32.TryParseメソッドの使用- Option 型と
match式の組み合わせ String.Trim()を使った空白文字の除去GameResult型へのNaNの追加match式を使った Option 型の値に応じた処理の分岐
Option 型と tryParse 関数を使うことで、ユーザーからの不正な入力を安全に処理できるようになった。また、GameResult 型に NaN を追加し、match式で分岐することで、エラーハンドリングをより明示的かつ安全に行うことができるようになった。
F# では、例外処理よりも、Option 型などの型システムを使ったエラーハンドリングが好まれる。型システムを活用することで、エラーハンドリングをより明示的かつ安全に行うことができる。