0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HaskellのApplicativeのモヤモヤ解消

0
Last updated at Posted at 2026-05-27

HaskellのApplicative(アプリカティブ)の説明を読むと、まあ使い方はわかるのだけど、なんだか意味がしっくりこないモヤモヤが残るので、Gemini先生に教えを請うた。

少し霧が晴れた気がするので、貼り付けておく。


Haskellの Applicative(アプリカティブ)は、一言でいうと「文脈(コンテキスト)に入った関数を、文脈に入った値に適用する」ための仕組みです。

「使い方はわかるけど意味がしっくりこない」という状態、非常によく分かります。Maybe やリストで Just (+1) <*> Just 2 のようなコードを見ても、「それ fmap やパターンマッチで良くない?」と思ってしまいますよね。

Applicative の真価(意味)が最もスッキリ理解できるのは、「複数の『失敗するかもしれない値』を組み合わせて、新しい値を作るとき」です。

理解を深めるための、実用的でちょうどいい難易度の例題を用意しました。一緒に解きながら意味を落とし込んでいきましょう!


例題:ユーザー登録のバリデーション(検証)

Webアプリのユーザー登録画面をイメージしてください。
ユーザーから入力された「名前」と「年齢」を使って、システムに登録するための User データを型安全に作りたいとします。

1. 準備(データの定義)

まずは必要な型を定義します。

-- 最終的に作りたいクリーンなデータ構造
data User = User { name :: String, age :: Int } deriving Show

-- バリデーション用関数(条件に合わない場合は Nothing を返す)
-- 1. 名前は空であってはならない
checkName :: String -> Maybe String
checkName "" = Nothing
checkName s  = Just s

-- 2. 年齢は0歳から120歳まで
checkAge :: Int -> Maybe Int
checkAge n | n >= 0 && n <= 120 = Just n
           | otherwise          = Nothing

2. 問題:これらを組み合わせて makeUser を作成せよ

画面から入力された未検証のデータ rawNamerawAge を受け取り、両方のチェックをパスした場合のみ Just User を返し、どちらか一方でも失敗したら Nothing を返す関数 makeUser を作ってください。

makeUser :: String -> Int -> Maybe User
makeUser rawName rawAge = ???


解説:なぜ Applicative なのか?

この問題を解くアプローチを3ステップで見ていくと、Applicative の意味が「これしかない!」というレベルでしっくりきます。

アプローチA:パターンマッチ(泥臭い方法)

まずは Applicative を使わずに書くとどうなるでしょう。

makeUserPattern :: String -> Int -> Maybe User
makeUserPattern rawName rawAge = 
    case checkName rawName of
        Nothing -> Nothing
        Just n  -> case checkAge rawAge of
            Nothing -> Nothing
            Just a  -> Just (User n a)

「もし名前がOKなら、次は年齢をチェックして…」と、いわゆるコールバック地獄(ネスト)になってしまいます。非常に面倒ですね。

アプローチB:fmap (<$>) だけを使ってみる

「Haskellには fmap があるじゃないか!」ということで、User 関数(中身は String -> Int -> User という2引数関数)に、まず名前のチェック結果を適用してみます。

-- checkName rawName の結果が Just "Alice" だとすると...
User <$> checkName rawName  -- 結果は: Just (User "Alice") という「関数が入ったMaybe」

ここで行き詰まります。手元にあるのは Just (User "Alice") という「関数がMaybeに包まれたもの」と、checkAge rawAge という「値がMaybeに包まれたもの」です。

fmap<$>)は、「普通の関数」を「文脈付きの値」に適用する道具なので、「文脈に入った関数」を適用することができません。

アプローチC:ここで登場するのが Applicative (<*>)!

「文脈に入った関数」を「文脈に入った値」に適用したい。これこそが Applicative の存在意義です。

makeUser :: String -> Int -> Maybe User
makeUser rawName rawAge = User <$> checkName rawName <*> checkAge rawAge

意味のイメージ図

  1. User <$> checkName rawName
    Maybe という箱の中に、「年齢を入れてくれればUserを作るよ」という状態の関数が入る。
  2. <*> checkAge rawAge
    Maybe の箱に入った関数と、Maybe の箱に入った年齢を、箱から出さずに合体させる。

もし checkNameNothing(エラーの箱)を返していたら、その後の <*> は何もせず、最終結果も自動的に Nothing になります。


演習問題:さらに項目を増やしてみよう

Applicative の本当の美しさは、検証する項目がいくつ増えてもコードの形が変わらないことにあります。

問題

User 型に「メールアドレス(String)」を追加したくなりました。
以下のコードの ??? を埋めて、makeUser2 を完成させてください。

data User2 = User2 { name2 :: String, age2 :: Int, email2 :: String } deriving Show

-- メールアドレスは '@' を含んでいなければならない
checkEmail :: String -> Maybe String
checkEmail s | '@' `elem` s = Just s
             | otherwise    = Nothing

makeUser2 :: String -> Int -> String -> Maybe User2
makeUser2 rawName rawAge rawEmail = ???

ヒント

パターンマッチ(case文)で書こうとすると、ネストが3重になって発狂しそうになりますが、Applicative なら <*> を1つ繋げるだけです。


この「複数の文脈付きの値を、一つの関数にまとめて流し込む(ただし途中で一つでも失敗したら全体が失敗になる)」という一連の流れの制御こそが、Applicative の持つ「意味」です。

演習問題の解答コードを考えてみて、しっくりくるか試してみてください!

0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?