Haskell初心者向け:モナドと`pure`を理解する
Haskellにおけるモナドとpure
モナドとは?
モナドは、型クラスであり、その型にはreturn
と>>=
という2つの関数が必要です。
return
は、値をモナド型にラップします。>>=
は、2つのモナド型アクションを結合します。
モナドは、様々な種類の計算を抽象化するために使用できます。例えば、以下のようなものがあります。
- 入出力処理
- 状態管理
- エラー処理
- 並行処理
pure
とは?
pure
は、値をモナド型にラップするための関数です。例えば、pure 1
は、Int
型の値1をMaybe
モナド型にラップします。
pure
は、以下の2つの理由で重要です。
- モナド型アクションの開始点として使用できます。
- モナド型アクションの中に純粋な値を注入できます。
モナドとpure
の関係
モナドとpure
は、密接な関係があります。
- モナドは、
return
と>>=
を使用して、副作用を持つ計算を抽象化します。 pure
は、値をモナド型にラップすることで、副作用のない純粋な値をモナド型アクションの中に注入できます。
モナドとpure
を組み合わせることで、副作用を持つ計算と純粋な値を統一的に扱うことができます。
例
以下は、Maybe
モナドを使用して、ファイルから値を読み取る例です。
readFile :: String -> IO String
maybeReadFile :: String -> Maybe String
maybeReadFile filename = do
content <- readFile filename
pure $ Just content
main = do
filename <- getLine
maybeResult <- maybeReadFile filename
case maybeResult of
Just content -> putStrLn content
Nothing -> putStrLn "File not found"
この例では、readFile
関数は、ファイルを読み込んでその内容をString
型として返します。しかし、readFile
関数は副作用を持つため、直接モナド型アクションの中で使用することはできません。
そこで、pure
を使用してreadFile
関数の結果をMaybe
モナド型にラップします。これにより、maybeReadFile
関数は、副作用を持つreadFile
関数をモナド型アクションの中に安全に含めることができます。
{-# LANGUAGE OverloadedStrings #-}
import Data.Maybe
-- ファイルから値を読み取る関数
readFile :: String -> IO String
-- Maybeモナドを使用してファイルから値を読み取る関数
maybeReadFile :: String -> Maybe String
maybeReadFile filename = do
content <- readFile filename
pure $ Just content
-- エントリポイント
main :: IO ()
main = do
-- ファイル名を取得
filename <- getLine
-- ファイルを読み込み、結果をMaybe型で取得
maybeResult <- maybeReadFile filename
-- 結果を処理
case maybeResult of
Just content -> putStrLn content
Nothing -> putStrLn "File not found"
このコードは、以下の手順で動作します。
getLine
を使用して、ユーザーからファイル名を入力maybeReadFile
を使用して、ファイルから値を読み込み、結果をMaybe型で取得case
式を使用して、Maybe型の結果を処理
- ファイルが存在し、読み込みに成功した場合は、
Just
の中身を取り出して出力 - ファイルが存在しない、または読み込みに失敗した場合は、"File not found"というメッセージを出力
このコードは、モナドとpure
を使用して、副作用を持つファイル読み込み操作を抽象化する方法を示しています。
改良点
以下の点について、コードを改良しました。
OverloadedStrings
言語拡張を使用し、文字列リテラルをより簡潔に記述- エラーメッセージをより分かりやすく
Eitherモナドを使用する
Either
モナドは、成功と失敗の2つの状態を表すために使用できます。
readFile :: String -> IO (Either String String)
maybeReadFile :: String -> Maybe String
maybeReadFile filename = do
result <- readFile filename
case result of
Left err -> Nothing
Right content -> Just content
main :: IO ()
main = do
filename <- getLine
maybeResult <- maybeReadFile filename
case maybeResult of
Just content -> putStrLn content
Nothing -> putStrLn "File not found"
このコードでは、readFile
関数は、成功時にはRight
、失敗時にはLeft
を返します。
maybeReadFile
関数は、case
式を使用して、Either
型の結果を処理します。
try構文を使用する
try
構文は、例外処理を行うために使用できます。
readFile :: String -> IO String
maybeReadFile :: String -> Maybe String
maybeReadFile filename = do
content <- try $ readFile filename
pure $ Just content
main :: IO ()
main = do
filename <- getLine
maybeResult <- maybeReadFile filename
case maybeResult of
Just content -> putStrLn content
Nothing -> putStrLn "File not found"
このコードでは、try
構文を使用して、readFile
関数の呼び出しを囲みます。
readFile
関数が失敗した場合、try
構文は例外を捕捉し、Nothing
を返します。
liftIOを使用する
liftIO
は、IOアクションをモナド型アクションに変換するために使用できます。
readFile :: String -> IO String
maybeReadFile :: String -> Maybe String
maybeReadFile filename = do
content <- liftIO $ readFile filename
pure $ Just content
main :: IO ()
main = do
filename <- getLine
maybeResult <- maybeReadFile filename
case maybeResult of
Just content -> putStrLn content
Nothing -> putStrLn "File not found"
このコードでは、liftIO
を使用して、readFile
関数をモナド型アクションに変換します。
これにより、readFile
関数を直接モナド型アクションの中で使用することができます。
どの方法を使用するべきか?
どの方法を使用するべきかは、状況によって異なります。
Maybe
モナドは、成功と失敗の2つの状態を明確に表現したい場合に適しています。Either
モナドは、成功と失敗の両方の情報を取得したい場合に適しています。try
構文は、例外処理を簡潔に記述したい場合に適しています。liftIO
は、IOアクションをモナド型アクションの中で使用したい場合に適しています。
haskell