Haskell

だれも Data.Maybe を教えてくれなかった

はじめに

この数年、 Haskell (以下、 H) を独習している。 すごい H をはじめ数冊の本を読み、 web でもたくさんの良記事に出あった。残念ながらすごい H になりきれていない私だが、それでも H 生活に小さな幸せを見出している。

ところで、モジュール Data.Maybe は、私にとってはとても大切な H の道具箱なのだが、それについての記事を見た記憶がないのだ。なぜだ、なぜ書く人がいないのか。仕方がないので、私が語ることにする。すでに書いている人がいたらごめんなさい。

モジュール Data.Maybe

名前から想像できる通り、 Maybe にまつわる関数群である。 base に入っているので、特にインストールなど必要ない。使うには import Data.Maybe とすればよい。

以下, Data.Maybe -- hackage に載っているものを順番に見ていこう。 (リンクは base-4.12.0.0 のもの)

data Maybe a

Maybe
data  Maybe a  =  Nothing | Just a

みんな大好き Maybe の定義。ここで詳述するつもりはないが、シンプルであるにもかかわらず、 Functor とか Monad とか多くの関数型言語の概念にからみ、手続き型言語との差別化に大きな役割を果たしている。 H に欠かせないもの、それが Maybe だ。

maybe

maybe
maybe :: b -> (a -> b) -> Maybe a -> b

私はよく使う。第一引数が Nothing の時の default 値、第二引数が Just の中身に作用させる関数。

title.hs
import Safe (atMay)
-- atMay: 失敗をあつかえる (!!)。 成功時は Just にくるんで値を返し,
-- インデックスに対応する値がないと Nothing を返す

title "男" = "君"
title "女" = "ちゃん"
title _ = "さん"

nameWithTitle s =
  let ws = words s
  in (ws !! 1) ++ maybe "閣下" title (ws `atMay` 3)

main = do
  putStrLn $ nameWithTitle "山田 太郎 5 男"
  putStrLn $ nameWithTitle "田中 花子 4 女"
  putStrLn $ nameWithTitle "小暮 デーモン 100056" -- 性別フィールドがない
$ stack runghc title.hs
太郎君
花子ちゃん
デーモン閣下

isJust, isNothing

isJust,isNothing
isJust :: Maybe a -> Bool
isNothing :: Maybe a -> Bool

JustNothing かを判定するときに便利、なのだが、そんなに使わないのではなかろうか。これらの関数を filter とかと組みあわせるよりも、 maybe とか catMaybes とか使う方がより H だと思う。

fromJust

fromJust
fromJust :: Maybe a -> a

Haskell をはじめた頃、 Data.Map.lookup の結果だったか、これ絶対 Just v なのに、 v を取りだせない、ググっても取り外し方見つかんない、と腹立たしく思ったものである。本当は、 functor とか monad とか、なんらかの方法でNothing をちゃんと手当して、値がかぶっている Just の皮をやさしく剥いてあげなければならなかったのだ。いまならわかる。

だがしかーし、 fromJust を使うと簡単に Just を剥けてしまう。乱暴だと思うのだが、ちょっとした作業で絶対 Just だとわかっている場合は、これを使えばよいだろう。

なお、これで Nothing を無理矢理剥くと例外を発射してくるので注意。

seibetsu1.hs
import Safe (atMay)
import Data.Maybe

showSeibetsu s =
  let ws = words s
  in (ws !! 1) ++ " " ++ fromJust (ws `atMay` 3)

main = do
  putStrLn $ showSeibetsu "山田 太郎 4 男"
  putStrLn $ showSeibetsu "田中 花子 5 女"
  putStrLn $ showSeibetsu "小暮 デーモン 100056"
$ stack runghc seibetsu1.hs
太郎 男
花子 女
デーモン seibetsu1.hs: Maybe.fromJust: Nothing

fromMaybe

fromMaybe
fromMaybe :: a -> Maybe a -> a

maybe の特別な場合。もしくは丁寧な fromJustNothing に対して default 値を与えられる。やさしいあなたはこれでそっと Just を剥いてあげて。

seibetsu2.hs
import Safe (atMay)
import Data.Maybe

showSeibetsu s =
  let ws = words s
  in (ws !! 1) ++ " " ++ fromMaybe "悪魔" (ws `atMay` 3)

main = do
  putStrLn $ showSeibetsu "山田 太郎 4 男"
  putStrLn $ showSeibetsu "田中 花子 5 女"
  putStrLn $ showSeibetsu "小暮 デーモン 100056"
$ stack runghc seibetsu2.hs
太郎 男
花子 女
デーモン 悪魔

listToMaybe, maybeToList

listToMaybe,maybeToList
listToMaybe :: [a] -> Maybe a
maybeToList :: Maybe a -> [a]

Nothing と空リストを結びつけるコマンド。 listToMaybe は、最初はなんじゃこりゃ、と思ったのだが、みんなのなかで最初に成功したやつだけがえらい、そいつにしか興味がない、という状況はよくあって、意外に使える。一方、 maybeToList は私はあまり使っていないのだが、たぶん使っている人は多いんだと思うよ。

とまあ、こんなことを書いている私だが 、実は listToMaybe の代わりに Safe.headMay を使うことが多い模様。

catMaybes

catMaybes
catMaybes :: [Maybe a] -> [a]

かなり H な関数である、と個人的には思っている。

失敗する関数をデータに適用することについて考えてみよう。

命令型言語なら do ループの中に if 文とか置いて、「危険なの」と言われた時にはなにもしないようにすることだろう。 Haskell でもそのように書くことはできる。

catMaybes はちがう。後先考えずにやってしまい、まずいことがおきたら無かったことにしてしまう、そんなことを可能にする。こちらの考え方のほうがより H なのである。

catMaybes
catMaybes :: [Maybe a] -> [a]
showAge.hs
import Safe (readMay)
import Control.Applicative ((<$>))
import Data.Maybe

nameAndAge :: String -> Maybe (String,Int)
nameAndAge s =
  let ws = words s
  in ((,) (head ws)) <$> readMay (ws !! 2)

printTpl (n,i) = putStrLn $ n ++ " " ++ show i  

main = do
  mapM_ printTpl . catMaybes . map nameAndAge . lines 
    =<< readFile "meibo.txt" -- データ行のみならずヘッダーも含まれたテキストファイル

$ cat meibo.txt 
天使幼稚園 ガブリエル組 クラス名簿
姓 名 年齢 性別
山田 太郎 4 男
田中 花子 5 女
小暮 デーモン 100056
$ stack runghc showAge.hs
山田 4
田中 5
小暮 100056

mapMaybe

あーこれ使ったことないなー、今度使ってみよー、こんな文章書いておいて mapMaybe 使ったことないなんて恥ずかしー、まだまだ私 H じゃないわー。

さいごに

以上の Data.Maybe の関数は、どれも簡単なものである。できる人ならプログラム中に同じ効果のある snippet をさくさく埋め込むことができるだろう。なくても問題ないのだ。

以前、私はそのようなコードを書いていた。いま読み返すと、そんなコードは、なんとういか、ガバガバな感じがする。一方、 Data.Maybe を使うと、気持ちよく締まった、まことに H なコードが書ける。締まったコードを書くのに役立つモジュールは他にも Data.Either, Data.Bool などがあるが、 Data.Maybe は格別だ。

H 初心者の方、 Data.Maybe の使い方をおぼえてより H なコードを書いてほしい。そして、初心者に手ほどきをする立場のあなた、是非、 Data.Maybe を教えてより H になれるよう、優しく導いてあげていただきたい。