Edited at

だれも 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 になれるよう、優しく導いてあげていただきたい。