MP in Haskell
MP = Monadic Programming
1. モナドとは何か
2. モナドの基本的な扱い方
3. モナド変換子の使い方
自己紹介
(defprofile
大橋 賢人 [OHASHI Kent]
:company 株式会社オプト テクノロジー開発2部
:github @lagenorhynque
:twitter @lagenorhynque
:languages [Python Haskell Clojure Scala English français Deutsch русский]
:interests [プログラミング 語学 数学])
モナド(Monad)とは
一言で言えば、 >>=(bind) できる型クラス(type class)。
m a -> (a -> m b) -> m b
型クラスFunctor, Applicativeの上位互換。
-- Haskell
class Applicative m => Monad m where
(>>=) :: forall a b. m a -> (a -> m b) -> m b
return :: a -> m a
...
// scalaz.Monad
trait Monad[F[_]] extends Applicative[F] with Bind[F] {
abstract def bind[A, B](fa: F[A])(f: (A) ⇒ F[B]): F[B]
abstract def point[A](a: ⇒ A): F[A]
...
}
モナドの具体例
例えば、 。Maybe
型クラスFunctor, Applicative, Monadのインスタンスになっている。
Functorの fmap / <$>
(a -> b) -> f a -> f b
n :: Maybe Int
n = Just 1
f :: Int -> String
f = x -> show x
-- fmap
fmap f n
-- <$>
f <$> n
-- >>= による実装
n >>= return . f
-- do記法による実装
do
a <- n
return $ f a
Applicativeの <*>
f (a -> b) -> f a -> f b
n :: Maybe Int
n = Just 1
f :: Maybe (Int -> String)
f = Just $ x -> show x
-- <*>
f <*> n
-- >>= による実装
n >>= a ->
f >>= g ->
return $ g a
-- do記法による実装
do
a <- n
g <- f
return $ g a
Monadの >>=
m a -> (a -> m b) -> m b
n :: Maybe Int
n = Just 1
f :: Int -> Maybe String
f = x -> Just $ show x
-- >>=
n >>= f
-- do記法による実装
do
a <- n
f a
同種のモナドを扱う場合
例えば、 Maybeを単独で使う場合
同種のモナドを扱う場合
(1) パターンマッチ
構造に注目して分解(unapply, destructure)する。
data User = User {
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userName :: User -> Maybe String
userName User {firstName = Just f, lastname = Just l} = Just $ f ++ " " ++ l
userName _ = Nothing
同種のモナドを扱う場合
(2) 高階関数
高階関数>>=, etc.を組み合わせる。
data User = User {
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userName :: User -> Maybe String
userName user =
firstName user >>= f ->
lastName user >>= l ->
return $ f ++ " " ++ l
同種のモナドを扱う場合
(3) do記法
モナドのためのシンタックスシュガーを活用する。
data User = User {
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userName :: User -> Maybe String
userName user = do
f <- firstName user
l <- lastName user
return $ f ++ " " ++ l
異種のモナドが混在する場合
例えば、 Maybeと を組み合わせてEither
Either e (Maybe a)として扱う必要がある場合
異種のモナドが混在する場合
(1) パターンマッチ
data User = User {
id' :: Int,
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userRole :: Int -> Either Error Role
userRole i = undefined
userInfo :: User -> Either Error (Maybe String)
userInfo user = case userRole $ id' user of
Right r -> case user of
User {firstName = Just f, lastName = Just l} ->
Right . Just $ f ++ " " ++ l ++ ": " ++ show r
_ ->
Right Nothing
Left e -> Left e
問題点
複数階層のモナドの分解・再構築が煩わしい
構造に強く依存しているため変更に弱い
パターンマッチがネストして書きづらく読みづらい
異種のモナドが混在する場合
(2) 高階関数
data User = User {
id' :: Int,
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userRole :: Int -> Either Error Role
userRole i = undefined
userInfo :: User -> Either Error (Maybe String)
userInfo user =
userRole (id' user) >>= r ->
return $ firstName user >>= f ->
lastName user >>= l ->
return $ f ++ " " ++ l ++ ": " ++ show r
問題点
構造を直接扱う必要はないが関数がネストして書きづらく読みづらい
異種のモナドが混在する場合
(3) do記法
data User = User {
id' :: Int,
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userRole :: Int -> Either Error Role
userRole i = undefined
userInfo :: User -> Either Error (Maybe String)
userInfo user = do
r <- userRole $ id' user
return $ do
f <- firstName user
l <- lastName user
return $ f ++ " " ++ l ++ ": " ++ show r
問題点
関数はネストしないがdo記法が連鎖して書きづらく読みづらい
モナド変換子(monad transformer)とは
一言で言えば、あるモナドに別のモナドを上乗せ(合成)したモナド。
ネストしたモナドをネストしていないかのように扱えるようになる。
e.g. , ,MaybeTEitherTListT
モナド変換子の生成と変換
-- M(モナド)とMaybeでネストしたモナド
mMaybeA :: M (Maybe a)
-- MaybeとMを合成したMaybeT
maybeTMA :: MaybeT M a
-- Maybe
maybeA :: Maybe a
-- M
mA :: M a
-- M (Maybe a) → MaybeT M a
MaybeT mMaybeA
-- MaybeT M a → M (Maybe a)
runMaybeT maybeTMA
-- Maybe a → M (Maybe a) → MaybeT M a
MaybeT $ return maybeA
-- M a → MaybeT M a
lift mA
モナド変換子の導入
ここではモナド変換子MaybeTを利用して
MaybeとEitherを合成してみる。
import Control.Monad.Trans.Class
import Control.Monad.Trans.Maybe
data User = User {
id' :: Int,
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userRole :: Int -> Either Error Role
userRole i = undefined
userInfo :: User -> Either Error (Maybe String)
userInfo user = runMaybeT $ do
r <- lift . userRole $ id' user
f <- MaybeT . return $ firstName user
l <- MaybeT . return $ lastName user
return $ f ++ " " ++ l ++ ": " ++ show r
Maybe aとEither e aをdo記法1つでシンプルに扱える
モナド変換子への変換がやや冗長
さらにリファクタ
モナド変換子への変換を関数として抽出してみる。
import Control.Monad.Trans.Class
import Control.Monad.Trans.Maybe
data User = User {
id' :: Int,
firstName :: Maybe String,
lastName :: Maybe String
} deriving Show
userRole :: Int -> Either Error Role
userRole i = undefined
fromMaybe :: Maybe a -> MaybeT (Either Error) a
fromMaybe = MaybeT . return
userInfo :: User -> Either Error (Maybe String)
userInfo user = runMaybeT $ do
r <- lift . userRole $ id' user
f <- fromMaybe $ firstName user
l <- fromMaybe $ lastName user
return $ f ++ " " ++ l ++ ": " ++ show r
Further Reading
Functor, Applicative, Monadのシンプルな定式化- Qiita
ScalaでFutureとEitherを組み合わせたときに綺麗に書く方法- scala
とか・・・
Scalaz Monad Transformers - Underscore
独習Scalaz — モナド変換子
Easy Monad
Haskell モナド変換子超入門- Qiita
All About Monads - Sampou.Org
Source Code
Gist - lagenorhynque - monad-transformers

MP in Haskell