部分関数をどう扱うか(spoonの紹介)

~~純粋な振りして例外投げる関数滅べばいいのに~~
**部分関数ダメゼッタイ**

# お行儀の悪い関数

Haskellにはいくつか「お行儀の悪い関数」があります。ここで言う「お行儀の悪い」とはIOが付いているわけでもないのに例外を投げる関数のことです。数学やhaskellでは「部分関数(partial functions)」と呼ばれているようです。今回はその「お行儀の悪い関数」をどう扱うかという話をします。はっきりと言ってバッドノウハウの類いの話です。

そもそもなぜ部分関数が問題であるのかは[Partial Function Considered Harmful - 純粋関数空間](http://tanakh.jp/posts/2011-12-25-partial-function-considered-harmful.html)で詳しく説明されているのでこちらも併せて読むとより良いと思います(丸投げ

haskellにおける部分関数で有名なものに`head`, `last`, `tail`, `init`がありますね。これらの関数は空リストを渡すと例外を投げます。`div`は第二引数に`0`を渡すと、また`!!`はリストの長さ以上のインデックスを指定すると例外を投げます。あとは`read`や`fold(l|r)1`などなど。

また、`undefined`や`error`や網羅的ではないパターンマッチを使った関数も部分関数になるでしょう。

```hs
> head []
> last []
> tail []
> init []
-- *** Exception: Prelude.head: empty list

> 10 `div` 0
-- *** Exception: divide by zero

> [1] !! 1
-- *** Exception: Prelude.!!: index too large

unknown = undefined
> unknown
-- *** Exception: Prelude.undefined

data Piyo = Foo | Bar

partial :: Piyo -> Int
partial Foo = 1

> partial Bar
-- *** Exception: Non-exhaustive patterns in function
```

このような関数は型を見ても部分関数であるか判断できず、突然例外を投げるため非常にやっかいです。

# 対策

## 例外を投げる値を弾く関数でラップする

一番シンプルで、おそらくベストな方法は例外を投げる値かどうかを判断する方法です。

```hs
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead xs = Just . head $ xs
```

すでにいくつかの関数では[safe](https://hackage.haskell.org/package/safe)に例外を投げないような関数が定義されているのでこちらを使うと良いでしょう。

## IOで包む

問題はここからです。

どのような値で例外を投げるか知ることができれば上記のような方法で解決できますが、何らかの理由でどのような値で例外を投げるか知ることができなかったり、あるいは例外を投げる条件が非常にややこしかったりするとどうしましょう。

今回は`head`がそのような面倒な関数だとします。

こういう時は`deepseq + return`を使うか、`evaluate`を使って`IO`で包むと`Control.Exception`の`catch`によってキャッチすることができるようになります。

```hs
import Control.Exception

bomb :: Int
bomb = head [] --WHNFの時点で例外

deepseq bomb (Just `fmap` return bomb) `catch` \(SomeException _) -> return Nothing
-- IO Nothing

Just `fmap` evaluate bomb `catch` \(SomeException _) -> return Nothing
-- IO Nothing
```

　`IO`の伝播が問題にならない場合はこれで解決です。

ちなみに`evaluate`の例で`(evaluate . Just) bomb`とすると`Just bomb`の時点で弱頭部正規形(weak head normal-form, WHNF)になるため、例外は捕捉できません。

理由は`deepseq`はNFまで評価します(従って`NFData`のインスタンスである必要があります)が、`evaluate`はWHNFまでしか評価しない点です。従って`evaluate`を使う場合は、WHNFまで評価された時点で例外を投げるかどうか気をつける必要があります。

## unsafe

上記のように`IO`で包めば例外をキャッチすることができるようになりますが、今度は`IO`を引きずり回すことになります。`IO`を扱うには`IO`の文脈でなければならないので、それを使う関数にも`IO`が伝播していきます。

どうしても`IO`を伝播させたくない場合、最後の手段である`unsafePerformIO`を使えばなんとかなります。部分関数が内部でunsafeを使っていない限りは例外を投げること以外は純粋なはずなので、実装的には変なことは起こらないはず・・・多分・・・きっと・・・。

```hs
import Control.Exception
import System.IO.Unsafe

bomb :: Int
bomb = head []

unsafePerformIO $ Just `fmap` evaluate bomb `catch` \(SomeException _) -> return Nothing
-- Nothing
```

# spoon

やっと[spoon](https://hackage.haskell.org/package/spoon)の登場です。spoonはこのような部分関数&&例外を投げる値を弾けない&&`IO`をどうしても伝播させたくない時に使います。

`spoon`は内部で`deepseq + return`を、`teaspoon`は内部で`evaluate`をしてから`unsafePerformIO`をしています。

```hs
import Control.Spoon

spoon $ head [1]
-- Just 1

spoon $ head []
-- Nothing
```

前述のように`evaluate`ではWHNFまでしか評価しないので使うときは注意しましょう。

spoonも内部では`unsafePerformIO`を使っているので根本的解決にはなっていませんが、部分関数をどうにかするためという目的が分かる分、直接`unsafePerformIO`を使うよりも心理的抵抗は小さいのではないでしょうか。

# まとめ

今回は「部分関数をどう扱うか」ということについて考えてみましたが、先ほど書いた通り「事前に入力値をチェックする関数で包む」のがベストというのは変わらないでしょう。

チェックすることが現実的でない時は、もうしょうがないです。「こんな関数作ったやつが悪いんだー」と思いながら`unsafe`を使うことも考えます。せめてもの足掻きとして、[spoon](https://hackage.haskell.org/package/spoon)を使って心の平穏を保ちましょう。決して`unsafePerformIO`やspoonを使うことを推奨するわけではありません。「どうにもならない時の最後の手段がちょっとマシになるよ」という思いでこの記事を書きました。バッドノウハウ。

また、このような悲劇を生まないためにも部分関数作るのはかなり強い理由がない限り避けましょう。

# 参考
[Partial Function Considered Harmful - 純粋関数空間](http://tanakh.jp/posts/2011-12-25-partial-function-considered-harmful.html)
[safe: Library of safe (exception free) functions](https://hackage.haskell.org/package/safe)
[spoon: Catch errors thrown from pure computations](https://hackage.haskell.org/package/spoon)