mp in haskell
TRANSCRIPT
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 CodeGist - lagenorhynque - monad-transformers