2012-01-15
■[Haskell] Maybe
Preludeを上から順に…と思ったら、早速Maybeで心折れそうです。
Maybeについて。
data Maybe a = Nothing | Just a
定義はこんな感じで、Maybeの中は Nothing か Just a のどちらかです。値がある場合はその値(Just a)、ない場合は何もない(Nothing)という感じ。
このNothingは、ある種の失敗を表していて、Javaでいうnullみたいな感じでしょうか。でもnullよりMaybeが良いのは、ちゃんと型にMaybeが付いて失敗の可能性が明らかに分ることや、その処理の楽さでしょうか…。
Maybeと一緒に使うのがmaybe関数です。
maybe :: b -> (a -> b) -> Maybe a -> b
引数にコメントをつけると
maybe :: b -- デフォルト値 -> (a -> b) -- 値があった場合に適用する関数 -> Maybe a -- 対象の値 -> b -- 戻り値
ということで、例えばですが、ユーザーがすでにログインしていたらログイン名を使い、そうでなければログインリンクを表示するという処理があったとすると…
showName :: Maybe String -> String showName mname = maybe "ログイン" (\name -> "こんにちは、" ++ name ++"さん") mname
と書けます。まぁこんな感じでパターンマッチしてもいいのですが…
showName2 :: Maybe String -> String showName2 mname = case mname of Nothing -> "ログイン" Just name -> "こんにちは、" ++ name ++ "さん"
maybeの方が短いけど、ちょとみづらいかも。。ところで、Maybe型のデータはよく変数名の頭にmをつけたりします。Maybeから取り出したらmを取ります。
実はMaybeのためだけの関数は少なくて、Haskell標準ライブラリであるPreludeに定義されているのはmaybe関数くらいでしょうか…。もう少しだけData.Maybeライブラリにありますが、すぐ覚えられる程度しかないですね…。
他に値を返したり返さなかったりする関数の戻り値がMaybeになっていることがあります。たとえばPrelude内でいうとlookupとか
lookup :: Eq a => a -> [(a, b)] -> Maybe b
これはキーバリューの組のリストから、キーを指定してバリューを取ってくる関数です。戻り値がMaybeなので、さっきのmaybe関数と一緒に使うと便利そうです。
values :: [(String,Int)] values = [("人参",100), ("大根", 250), ("トマト", 150)] howMuch :: String -> String howMuch name = maybe "見つかりません" ((++"円").show) (lookup name values)
単にデータとしてのMaybeはこんなものです。ところでMaybeのインスタンスの顔ぶれがなかなかすごいですね…。
Monad Maybe Functor Maybe Typeable1 Maybe MonadFix Maybe MonadPlus Maybe Applicative Maybe Foldable Maybe Traversable Maybe Alternative Maybe Eq a => Eq (Maybe a) Data a => Data (Maybe a) Ord a => Ord (Maybe a) Read a => Read (Maybe a) Show a => Show (Maybe a) Generic (Maybe a) Monoid a => Monoid (Maybe a)
MaybeはFunctorのインスタンスになっています。Functorとは簡単に言ってしまうと、値を入れる箱=コンテナのような性質です。Maybeは定義のとおり、値aをコンテナの中に持っています。
data Maybe a = Nothing | Just a ↑これ
普通は、さっきみたmaybe関数とかパターンマッチしないと、この中のaには触れないんですが、Functorクラスが唯一提供するfmap関数を使うと、このコンテナに触れずに、中の値に関数を適用することができるわけです。
難しい話は抜きに、実際に使ってみると、たとえば名前があったら何か処理をして、なければ何もしない、というのは…
baka :: Maybe String -> Maybe String baka mname = fmap (++"は馬鹿") mname -- こんな結果になる baka (Just "lostdog") ---> Just "lostdogは馬鹿" baka (Nothing) ---> Nothing
という感じです。fmapは意外と使いどころがある気がします。
かなり長くなってますが…、MaybeはMonadクラスのインスタンスになっています。モナドとは何か?的な話はよく語られるんですが、定義だけ言ってしまえば、このMonadクラスのインスタンスであるものをモナドと呼びます。この場合はMaybeモナドとか呼びます。
モナドの性質は、一言でいうと、通常のコンテキストに機能を付加するものでしょうか…。Maybeモナドは「失敗するかも」とか「失敗してもいいよ」というような機能を付加します。Maybeモナドを使えば、失敗をうまくハンドリングできるというわけです。
Maybeモナドの実際は、超簡単にいうと、途中でこけたら(値がNothingになったら)、全体の結果もNothing。全部うまくいったら(値がJustだったら)値が返ってくるというものです。
-- Nothingが途中にひとつでも入ったら、全体もNothing maybeTest1 = Just 1 >> Just 2 >> Nothing >> Just 3 ---> これはNothing -- 全部がJustのときだけ、Justが返る maybeTest2 = Just 1 >> Just 2 >> Just 3 >> Just 4 ---> これはJust 4
たとえば、ユーザー入力の処理なんかはそんな感じですね。モナドなのでdo構文が使えます。
-- 全部の入力が必須。ひとつでもNothingだったら、全体がNothing getInput :: Maybe (String, Int, String) getInput = do name <- getName age <- getAge mail <- getMail return (name, age, mail) getName :: Maybe String -- 実装は省略... getAge :: Maybe Int getMail :: Maybe String
失敗のハンドリングがコードとしては書かれていませんが、Maybeモナド内では失敗は自動でハンドリングされるので、わざわざ書かなくてよいわけです(そういう機能がコンテキストに追加されたわけなので)
追記
モノイドという性質が結構役に立つよと言われはじめたのは、いつからかは忘れましたが、そんなに古くなかったはずで、、というわけで、モノイドというのは大雑把にいうと、次のような性質です。
- 足し算できる
- その足し算の0となるものがある
どうも最近はMonadにモノイドを足してMonadPlusを作るより、Applicativeにモノイドを足してAlternativeという流れのほうが好まれるっぽいそうなので、Alternativeクラスの説明を入れておきます。
class Applicative f => Alternative f where empty :: f a -- 0に相当するもの (<|>) :: f a -> f a -> f a -- 足し算に相当するもの
Alternativeの定義は上のとおりです。そしてMaybe型はもちろんこのインスタンスになっています。
instance Alternative Maybe where empty = Nothing Nothing <|> p = p Just x <|> _ = Just x
Alternativeの名のとおり、失敗したら次へフェイルオーバーするというような感じですね…。最初にJustが見つかるまで、頭から見ていくということなので
-- これをimport import Control.Applicative Nothing <|> Nothing <|> Just 2 <|> Just 3 <|> Nothing -- Just 2 が返る Just 3 <|> Just 2 <|> Just 1 -- Just 3 が返る Nothing <|> Just 1 -- この2つは同じこと empty <|> Just 1
という感じです。モノイドとしての性質はMonadPlusと同じなので、ほかの例は下の補足を参考ということで。。
補足
補足のMonadPlusです。これはMonadにモノイドを足したクラスで、名前のとおり、足し算ができるという性質です(正確にはモノイドという性質です、ただモノイドの性質だけならば、別にモナドを前提にする必要がないのですが…)
mzero :: m a -- 足し算の零元 mplus :: m a -> m a -> m a -- 足し算
MaybeのMonadPlusのインスタンス定義はこんな感じです。
instance MonadPlus Maybe where mzero = Nothing Nothing `mplus` ys = ys -- 左がNothingなら、右を返す xs `mplus` _ys = xs -- 左がJustなら、左を返す
ということは
-- mplusを使うときはこれをimport import Control.Monad -- 左から最初に見つかったJustを返す. これはJust 2が返る plusTest1 = Nothing `mplus` Just 2 `mplus` Just 3 -- 全部Nothingだったら、結果もNothing plusTest2 = Nothing `mplus` Nothing `mplus` Nothing
最初はこんなのいつ使うの?という感じだったみたいですが、有用な例がいくつか提示されたりして、結構使えるじゃんということになってるようです…。
たとえば、ユーザーの連絡先が複数あるとして、優先順位の高い順に探して最初に見つかったところに連絡するとすると…
-- email1, email2, phone の順で連絡先を探して、最初に見つかったものを返す contact :: Maybe String contact = getEmail1 `mplus` getEmail2 `mplus` getPhone -- 実はmsum関数を使うと同じことができる contact2 = msum [getEmail1, getEmail2, getPhone] getEmail1, getEmail2, getPhone :: Maybe String -- 実装は省略
という具合です。Maybeはよく使うので少し長くなってしまいましたが、大体こんなところかと思います…。これでMaybeマスターだぜ!
- 9 http://t.co/R3pXhrwq
- 5 http://t.co/Cg55x6vW
- 5 http://t.co/ngypE3Pz
- 4 http://htn.to/B94GNj
- 3 http://reader.livedoor.com/reader/
- 3 http://www.google.co.jp/url?sa=t&rct=j&q=haskell maybe&source=web&cd=6&ved=0CE8QFjAF&url=http://d.hatena.ne.jp/Lost_dog/20120115/1326653279&ei=PM8qT8zVNanNmQX99pzWDw&usg=AFQjCNHnRmFpTT3i6ujBPtSxKvayDfWyKQ&sig2=SLU4dgHzx2xfNQaDvA5tEA
- 3 http://www.google.co.jp/url?sa=t&rct=j&q=haskell+maybe&source=web&cd=15&ved=0CEgQFjAEOAo&url=http://d.hatena.ne.jp/Lost_dog/20120115/1326653279&ei=fRdFT7O5MOiWiQfAmqSBAw&usg=AFQjCNHnRmFpTT3i6ujBPtSxKvayDfWyKQ
- 2 http://d.hatena.ne.jp/keyword/戻り値
- 2 http://ezsch.ezweb.ne.jp/search/?query=いじわる&start-index=16&adpage=4&ct=1301&sr=0104&t=20120117151320&filter=1
- 2 http://k.hatena.ne.jp/keywordblog/Haskell