tryとmatchの皮算用 - 例外処理か、振り分けか


F#はMicrosoftの[Don Syme](https://twitter.com/dsyme "https://twitter.com/dsyme")氏が開発したプログラミング言語で、命令型、オブジェクト指向、関数型の要素を併せ持つマルチパラダイム言語と[位置付けられています](https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp "https://msdn.microsoft.com/visualfsharpdocs/conceptual/visual-fsharp")。実装はオープンソースの[MITライセンス](https://github.com/fsharp/fsharp/blob/master/License.txt "https://github.com/fsharp/fsharp/blob/master/License.txt")のもと、[githubのリポジトリ](https://github.com/fsharp/fsharp "https://github.com/fsharp/fsharp")で公開されています。

実行環境は.NET Frameworkおよび.NET CoreやMonoといったマルチプラットフォームに対応していますが、サポート状況については各プラットフォームごとの情報を参照してください(「[F# の概要](https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index "https://docs.microsoft.com/ja-jp/dotnet/fsharp/get-started/index")」など)。

なお、ここで対象とするF#のバージョンは4.1([FSharp.Core](https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core "https://github.com/fsharp/fsharp/tree/master/src/fsharp/FSharp.Core") 4.4.1.0)とします。

# マルチパラダイムだからこそ...

F#はマルチパラダイムのプログラミング言語だからこそ抱える葛藤があります。それはどんなスタイルでコーディングするかです。ここでは例外処理に関して、それぞれのスタイルを考えてみます。

# 例外処理のスタイル

例外処理とは、通常の処理をこれ以上続けられなくなったときに行う処理のとことですが、F#では大きく分けて2つのスタイルが考えられます。それは__例外オブジェクトの発生__と__処理の振り分け__です。

##  例外オブジェクトの発生(try-with, try-finally)

`List.find`関数はリストの中から目的の値を見つける処理を行います。値が見つかれば、それが戻り値となりますが、値が見つからないときは「System.Collections.Generic.KeyNotFoundException」という__例外オブジェクト__を発生させます。

例外オブジェクトが発生したときは[try-with](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-with-expression "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-with-expression")や[try-finally](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-finally-expression "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-finally-expression")を使うと例外処理に移行できます。これらがなければ処理がその場で停止されます。

```fsharp:例外オブジェクトの発生(例外処理のスタイル)
try
  [1..5]
  |> List.find (fun n -> n < 0)
  |> printfn "%dを見つけました"  // List.findで例外オブジェクトが発生しなければ実行
with
  :? System.Collections.Generic.KeyNotFoundException
     -> printfn "見つかりませんでした"  // List.findで例外オブジェクトが発生したら実行
```

##  処理の振り分け(match-with, if-then)
`List.tryFind`関数もリストの中から目的の値を見つける処理を行いますが、値が見つかれば`Some n`(nが見つかった値)、値が見つからなければ`None`となります。その後の処理は関数の結果をもとに[match-with](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/match-expressions "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/match-expressions")や[if-then](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/conditional-expressions-if-then-else "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/conditional-expressions-if-then-else")で処理を振り分けます。

```fsharp:処理の振り分け(例外処理のスタイル)
match [1..5] |> List.tryFind (fun n -> n < 0) with  // 処理の振り分け
| Some n -> printfn "%dを見つけました" n   // 通常処理
| None   -> printfn "見つかりませんでした"  // 例外処理
```

# 例外オブジェクトによる例外処理

例外オブジェクトとは、例外の内容を表すもので、[System.Exception](https://docs.microsoft.com/ja-jp/dotnet/api/system.exception?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.exception?view=netframework-4.7.1")クラス(F#では[exn](https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.exn-type-abbreviation-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.exn-type-abbreviation-%5bfsharp%5d"))を継承するサブクラスで表されます。.NET環境ではさまざまな例外オブジェクトが定義済みで、自動的にこれらが生成されるようになっているメソッドもたくさん提供されています。

## 例外処理への移行

例外が発生した後、例外処理に移行するには[try-with](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-with-expression "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-with-expression")や[try-finally](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-finally-expression "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-finally-expression")を使います。両者は役割が異なり、同時に使うこともあります。

### try-withによる例外処理
[try-with](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-with-expression "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-with-expression")は`try`から`with`までの間で行われる処理の途中で例外が発生すると`with`以下の処理に移行します。`with`以下では、例外オブジェクトの内容に応じて処理を振り分けます。

重要なのは、`try-with`間で行われた処理の結果と`with`以下の処理の結果とが同じ型でなければならないということです。

以下は`List.find`関数の実行結果を得る処理ですが、`try-with`間の結果の型が`int`となるため、`with`以下の結果の型も`int`となるようにしなくてはなりません。

```fsharp:try-with間とwith以下の結果の型は同じにする
try
  [1..5] |> List.find (fun n -> n < 0)  // 見つかれば結果はint
with
  | _ -> -1  // 見つからなくても結果をintにする
```

`with`以下での処理の振り分け方は`match-with`のパターンマッチとよく似ています。`match-with`との違いは、パターンマッチの対象が例外オブジェクトであることと、パターンに当てはまらない例外オブジェクトが発生したときに処理が停止する場合があることです。

以下は画面から入力されたファイル名のテキストファイルを読み込み、内容を表示する処理です。

```fsharp:with以下の処理の振り分け
open System
open System.IO

try
  (* 指定されたテキストファイルの内容を表示 *)
  printf "ファイル名を入力してください = "
  Console.ReadLine() |> File.ReadAllText |> printfn "%s"
with
  (* 例外処理の振り分け *)
  | :? ArgumentException  // ファイル名が入力されていない
       -> printfn "ファイル名を入力してください"
  | :? FileNotFoundException as ex  // 指定されたファイルが見つからない
       -> printfn "%s" ex.Message
  | ex -> printfn "%A" ex  // その他の例外(アクセス不能など)
```

ファイル名が入力されていないときは`System.ArgumentException`が発生するため、`with`以下の`:? ArgumentException`というパターンで振り分けられた処理を行います。`:? FileNotFoundException as ex`は、ファイルが見つからなかったときのパターンで、例外処理で使う例外オブジェクトを`ex`で表すようにしています。これら以外の例外が発生したときは、例外オブジェクト(`ex`)の内容を文字列化して表示します。

### try-finallyによる例外処理
[try-finally](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-finally-expression "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/the-try-finally-expression")は`try-finally`間の処理で例外が発生していても、発生していなくても`finally`以下の処理を行います。この処理は`with`以下のようなパターンマッチは行われません。

以下は`try-finally`間で`List.find`関数を実行する処理ですが、`finally`以下は値が見つかっても、見つからなくても実行されます。

```fsharp:try-finallyによる例外処理
try
  // try-finally間では例外が発生する可能性のある処理を実行
  [1..5]
  |> List.find (fun n -> n < 0)
  |> printfn "%dを見つけました"
finally
  // finally以下は値が見つかっても見つからなくても実行される
  printfn "処理は終了しました"
```

### try-withとtry-finallyの組み合わせ
`try-finally`内で`try-with`をネストさせると、例外が発生したときに`with`以下の処理を行った後、`finally`以下の処理が行われます。両者のネストを逆にすると、例外が発生したときに`finally`以下の処理を行った後、`with`以下の処理が行われます。

```fsharp
try
  try
    [1..5]
    |> List.find (fun n -> n < 0)
    |> printfn "%dを見つけました"
  with
    // 例外が発生したときに実行される
    :? System.Collections.Generic.KeyNotFoundException
       -> printfn "見つかりませんでした"
finally
  // 例外が発生してもしなくてもtry-withの後で実行される
  printfn "処理は終了しました"
```

## 例外を発生させる関数
例外処理では、ただ発生した例外オブジェクトを受け止めるだけではなく、関数で例外オブジェクトを発生させることもできます。そうした関数には、発生させる例外オブジェクトの種類が決まっているものと、引数で種類を指定するものとがあります。発生させる例外オブジェクトの種類は処理の状況に合ったものも選択してください。

### nullArg パラメータ名<br>string -> 'T
[System.ArgumentNullException](https://docs.microsoft.com/ja-jp/dotnet/api/system.argumentnullexception?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.argumentnullexception?view=netframework-4.7.1")(引数がnullの例外)を発生させます。引数のパラメータ名は例外オブジェクトのメッセージに組み込まれます。

```fsharp
nullArg "arg1"

(* 発生する例外 *)
System.ArgumentNullException: 値を Null にすることはできません。
パラメーター名:arg1
```

### invalidArg パラメータ名 メッセージ<br>string -> string -> 'T
[System.ArgumentException](https://docs.microsoft.com/ja-jp/dotnet/api/system.argumentexception?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.argumentexception?view=netframework-4.7.1")(引数の例外)を発生させます。引数のパラメータ名とメッセージは例外オブジェクトのメッセージに組み込まれます。

```fsharp
invalidArg "arg1" "引数を設定してください"

(* 発生する例外 *)
System.ArgumentException: 引数を設定してください
パラメーター名:arg1
```

### invalidOp メッセージ<br>string -> 'T
[System.InvalidOperationException](https://docs.microsoft.com/ja-jp/dotnet/api/system.invalidoperationexception?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.invalidoperationexception?view=netframework-4.7.1")(操作の例外)を発生させます。引数のメッセージは例外オブジェクトのメッセージに組み込まれます。

```fsharp
invalidOp "この操作はできません"

(* 発生する例外 *)
System.InvalidOperationException: この操作はできません
```

### failwith メッセージ<br>string -> 'T
例外オブジェクトを発生させ、`with`以下では`Failure msg`(msgはメッセージ)のパターンで処理を振り分けられるようにします。そのため、例外処理では例外オブジェクト全体ではなくメッセージのみ使えます。

```fsharp
try
  failwith "Error!!"  // with以下の処理で使うメッセージ
with
  Failure msg -> printfn "%s" msg  // "Error!!"
```

### failwithf 書式 値 ...<br>Printf.StringFormat<'T, 'Result> -> 'T
`failwith`にメッセージの書式を設定する引数が追加されたものです。引数の書式と値に基づいて編集されたメッセージが`Failure msg`の`msg`に設定されます。

```fsharp
try
  failwithf "エラー (%s)" <| System.DateTime.Now.ToString()
with
  Failure msg -> printfn "%s" msg  // "エラー (2018/01/21 22:20:11)"
```

### raise 例外オブジェクト<br>System.Exception -> 'T
例外を発生させる関数で最も汎用的なものです。例外オブジェクトは引数で生成しておかなくてはなりません。

```fsharp
try
  raise <| System.NotSupportedException "サポートしていません"
with
  :? System.NotSupportedException as ex -> printfn "%s" ex.Message  // サポートしていません
```

### reraise<br>unit -> 'T
同じ例外オブジェクトを発生させる関数です。例外処理を呼び出し元の関数やメソッドに委ねるときに実行します。最初に発生した場所は例外オブジェクトの[StackTrace](https://docs.microsoft.com/ja-jp/dotnet/api/system.exception.stacktrace?view=netframework-4.7.1#System_Exception_StackTrace "https://docs.microsoft.com/ja-jp/dotnet/api/system.exception.stacktrace?view=netframework-4.7.1#System_Exception_StackTrace")プロパティや[TargetSite](https://docs.microsoft.com/ja-jp/dotnet/api/system.exception.targetsite?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.exception.targetsite?view=netframework-4.7.1")プロパティに記録されます。

```fsharp
let g () =
  try
    failwith "gのエラー" |> ignore  // 最初の例外オブジェクト発生
  with
    _ -> reraise()  // 呼び出し元fで同じ例外オブジェクトを発生させる

let f () =
  try
    g ()  // reraise() で例外オブジェクト発生
  with
    ex -> printfn "%A" ex  // reraise()で発生した例外オブジェクト

f()
```

## 例外オブジェクト型の定義
例外オブジェクト型は[System.Exception](https://docs.microsoft.com/ja-jp/dotnet/api/system.exception?view=netframework-4.7.1 "https://docs.microsoft.com/ja-jp/dotnet/api/system.exception?view=netframework-4.7.1")クラスのサブクラスですが、これを通常の継承とは違う[exception](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/exception-types "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/exception-handling/exception-types")を使って定義できます。これを使うと型の定義が短くて済み、オブジェクトに持たせる値の型も柔軟に定義できます。

たとえば`int * string`型の値持つ例外オブジェクト`CustomException`は以下のように定義できます。

```fsharp:例外を表す型の定義
exception CustomException of int * string  // of 以下が例外オブジェクトの型
```

こうして定義した`CustomException`は`try-with`間や`try-finally`間で`raise`関数によって例外として発生させることができます。そして`with`以下のパターンマッチでこの例外への対応を振り分けられらます。

```fsharp:例外オブジェクトの生成とパターンマッチ
try
  // 例外が発生させる処理
  CustomException (1, "Error!!") |> raise
with
  // exceptionで定義した例外のパターンマッチ
  | CustomException(n, s) -> printfn "%d %s" n s  // raise実行時に持たせた値を使える
```

# 処理の振り分けによる例外処理
例外処理とはいっても例外オブジェクトを使わず、これまでの結果を元に処理を振り分けるときもあります。振り分けは`match-with`や`if-then-else`で行いますが、結果を表す値にはさまざまな型が使われます。ここではそのうちのいくつかを紹介します。

## 'T option (Some result / None)
['T option](https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.option%5b%27t%5d-union-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.option%5b%27t%5d-union-%5bfsharp%5d")は`Some result`(resultが結果)と`None`という値をもつ型です。リスト内の値を探索する`List.tryFind`関数のように、結果が存在するときもあればそうでないときもある場合に使われます。

`match-with`で処理を振り分けると、値が見つかったときは`Some result`から`result`だけを取り出して処理を続行でき、`None`のときには例外処理を行うようにできます。

```fsharp:optionとmatch-withによる処理の振り分け
match [1..5] |> List.tryFind (fun n -> n > 2) with
| Some result -> printfn "%dを見つけました" result  // 通常処理
| None -> printfn "値は見つかりませんでした"         // 例外処理
```

## bool (true / false)
`true`もしくは`false`のどちらかで処理を振り分ける方法もあります。`System.Int32.TryParse`メソッドでは、文字列を数値に変換出来たら`(true, 数値)`、変換できなかったら`(false, _)`という結果になります。そのため、タプルの左の要素で結果の可否を判定して処理を振り分けます。

```fsharp:bool値で処理を振り分け
let s = "123"
match System.Int32.TryParse s with
| (true, n)  -> printfn "%d に変換できました" n
| (false, _) -> printfn "変換できませんでした"
```

## アクティブパターン(Choice)
[アクティブパターン](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/active-patterns "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/active-patterns")は結果に応じた識別子を自動的に設定できるもので、`match-with`とともに使うと処理の振り分けができます。__識別子は最大7つまで__定義できます。アクティブパターンの戻り値の型は[Choice](https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.choice%5b%27t1%2c%27t2%5d-union-%5bfsharp%5d "https://msdn.microsoft.com/visualfsharpdocs/conceptual/core.choice%5b%27t1%2c%27t2%5d-union-%5bfsharp%5d")になります。

```fsharp:アクティブパターンによる処理の振り分け
let (|Good|Bad|) n = if n >= 70 then Good else Bad  // int -> Choice<unit,unit>

let score = 73    // 点数
match score with  // 合否判定
| Good -> "合格"
| Bad  -> "不合格"
```

アクティブパターンでは識別子のどちらにも値を持たせられます。それぞれの値の型は違っていても対応できます。

```fsharp:値を持つアクティブパターンによる処理の振り分け
let (|Good|Bad|) n = if n >= 70 then Good n else Bad "不合格"  // int -> Choice<int,string>

let score = 30    // 点数
match score with  // 合否判定
| Good n -> printfn "%d点で合格" n
| Bad  s -> printfn "%s" s
```

これに`'T option`を組み合わせた[パーシャルアクティブパターン](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/active-patterns#partial-active-patterns "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/active-patterns#partial-active-patterns")では、結果が`Some result`のときは識別子が設定され、`None`のときには識別子が設定されません。パーシャルアクティブパターンの戻り値の型は`'T option`になります。

```fsharp:パーシャルアクティブパターンによる処理の振り分け
let (|Good|_|) n = if n >= 70 then Some n else None  // int -> int option

let score = 73    // 点数
match score with  // 合否判定
| Good s -> printfn "%d点は合格" s
| _      -> printfn "不合格"
```

## Result<'Succ, 'Fail>
[Result](https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/results "https://docs.microsoft.com/ja-jp/dotnet/fsharp/language-reference/results")は`Ok result`(resultは結果)と`Error fail`(failは例外を表す値)のどちらかを表す型です。`result`と`fail`の型は違っていても対応します。識別子を見ただけで結果が正常かエラーかが分かるのが特徴です。

```fsharp:Resultで処理を振り分け
let check n = if n >= 70 then Ok n else Error "不合格"  // int -> Result<int,string>

let score = 20
match check score with
| Ok n    -> printfn "%d点で合格" n
| Error s -> printfn "%s" s
```

---
例外処理のスタイルを、例外オブジェクトの発生と処理の振り分けとで考えてみました。

ここをご覧いただいた方々の参考になればと思います。