はじめに
この数年、 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
data Maybe a = Nothing | Just a
みんな大好き Maybe
の定義。ここで詳述するつもりはないが、シンプルであるにもかかわらず、 Functor とか Monad とか多くの関数型言語の概念にからみ、手続き型言語との差別化に大きな役割を果たしている。 H に欠かせないもの、それが Maybe
だ。
maybe
maybe :: b -> (a -> b) -> Maybe a -> b
私はよく使う。第一引数が Nothing
の時の default 値、第二引数が Just の中身に作用させる関数。
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 :: Maybe a -> Bool
isNothing :: Maybe a -> Bool
Just
か Nothing
かを判定するときに便利、なのだが、そんなに使わないのではなかろうか。これらの関数を filter
とかと組みあわせるよりも、 maybe
とか catMaybes
とか使う方がより H だと思う。
fromJust
fromJust :: Maybe a -> a
Haskell をはじめた頃、 Data.Map.lookup
の結果だったか、これ絶対 Just v
なのに、 v
を取りだせない、ググっても取り外し方見つかんない、と腹立たしく思ったものである。本当は、 functor とか monad とか、なんらかの方法でNothing
をちゃんと手当して、値がかぶっている Just
の皮をやさしく剥いてあげなければならなかったのだ。いまならわかる。
だがしかーし、 fromJust
を使うと簡単に Just
を剥けてしまう。乱暴だと思うのだが、ちょっとした作業で絶対 Just
だとわかっている場合は、これを使えばよいだろう。
なお、これで Nothing
を無理矢理剥くと例外を発射してくるので注意。
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 :: a -> Maybe a -> a
maybe
の特別な場合。もしくは丁寧な fromJust
。 Nothing
に対して default 値を与えられる。やさしいあなたはこれでそっと Just
を剥いてあげて。
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 :: [a] -> Maybe a
maybeToList :: Maybe a -> [a]
Nothing
と空リストを結びつけるコマンド。 listToMaybe
は、最初はなんじゃこりゃ、と思ったのだが、みんなのなかで最初に成功したやつだけがえらい、そいつにしか興味がない、という状況はよくあって、意外に使える。一方、 maybeToList
は私はあまり使っていないのだが、たぶん使っている人は多いんだと思うよ。
とまあ、こんなことを書いている私だが 、実は listToMaybe
の代わりに Safe.headMay
を使うことが多い模様。
catMaybes
catMaybes :: [Maybe a] -> [a]
かなり H な関数である、と個人的には思っている。
失敗する関数をデータに適用することについて考えてみよう。
命令型言語なら do ループの中に if 文とか置いて、「危険なの」と言われた時にはなにもしないようにすることだろう。 Haskell でもそのように書くことはできる。
catMaybes
はちがう。後先考えずにやってしまい、まずいことがおきたら無かったことにしてしまう、そんなことを可能にする。こちらの考え方のほうがより H なのである。
catMaybes :: [Maybe a] -> [a]
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 になれるよう、優しく導いてあげていただきたい。