4
3

More than 1 year has passed since last update.

モナドってなにさ?

Last updated at Posted at 2019-12-02

はじめに

ちょいと Haskell やってみる。むずい。
私は分析は得意ではないが、把握は得意な方です。
そう、私はジェネラリストです。

モナドとは?

Haskell 対話環境が教えてくれる。
$ ghci

ghci(Haskell)
GHCi, version 8.0.2: http://www.haskell.org/ghc/  :? for help
Prelude> :info Monad
class Applicative m => Monad (m :: * -> *) where
  (>>=) :: m a -> (a -> m b) -> m b
  (>>) :: m a -> m b -> m b
  return :: a -> m a
  fail :: String -> m a
  {-# MINIMAL (>>=) #-}
  	-- Defined in ‘GHC.Base’
instance Monad (Either e) -- Defined in ‘Data.Either’
instance Monad [] -- Defined in ‘GHC.Base’
instance Monad Maybe -- Defined in ‘GHC.Base’
instance Monad IO -- Defined in ‘GHC.Base’
instance Monad ((->) r) -- Defined in ‘GHC.Base’
instance Monoid a => Monad ((,) a) -- Defined in ‘GHC.Base’

わからん。が、
class Applicative m => Monad (m :: * -> *) となっていて、
意味は「mMonad であるならば、mApplicative でなければならない」と。

アプリカティブとは?

ghci
Prelude> :info Applicative
class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
  (*>) :: f a -> f b -> f b
  (<*) :: f a -> f b -> f a
  {-# MINIMAL pure, (<*>) #-}
  	-- Defined in ‘GHC.Base’
instance Applicative (Either e) -- Defined in ‘Data.Either’
instance Applicative [] -- Defined in ‘GHC.Base’
instance Applicative Maybe -- Defined in ‘GHC.Base’
instance Applicative IO -- Defined in ‘GHC.Base’
instance Applicative ((->) a) -- Defined in ‘GHC.Base’
instance Monoid a => Applicative ((,) a) -- Defined in ‘GHC.Base’

やはりだが「Applicative であるためには Functor でなければならない」と。

ファンクターとは?

ghci
Prelude> :info Functor
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
  {-# MINIMAL fmap #-}
  	-- Defined in ‘GHC.Base’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’

制約の連鎖が止まった。

再びモナドとは?

ファンクターの拡張版をアプリカティブという。
アプリカティブの拡張版をモナドという。

再びファンクターとは?

Haskell
-- '引数 -> 返り値'(ラムダ抽象)
class Functor (f :: * -> *) where -- '*' は具体型を表す
  fmap :: (a -> b) -> f a -> f b  -- 'a' 'b' は任意型を表す

f がファンクターであるためには fmap という関数を持っていなければならない。
「オレが世界征服を果たすためには fmap 貫光殺砲を体得しなければならない」(?

map と聞けば反射的にリストを思い浮かべる。

ghci
> :type map
map :: (a -> b) -> [a] -> [b]
--  :: (a -> b) -> [] a -> [] b と等価
-- '[]' はリスト生成前置関数

fmapmap を見比べると f = [] だなと。
リストはファンクターなのかもしれないと。

ghci
Prelude> :info Functor
class Functor (f :: * -> *) where
  fmap :: (a -> b) -> f a -> f b
  (<$) :: a -> f b -> f a
  {-# MINIMAL fmap #-}
  	-- Defined in ‘GHC.Base’
instance Functor (Either a) -- Defined in ‘Data.Either’
instance Functor [] -- Defined in ‘GHC.Base’  <== ここだよー くさかりまさおー
instance Functor Maybe -- Defined in ‘GHC.Base’
instance Functor IO -- Defined in ‘GHC.Base’
instance Functor ((->) r) -- Defined in ‘GHC.Base’
instance Functor ((,) a) -- Defined in ‘GHC.Base’

GHC.Base を覗くと、

GHC.Base
instance Functor [] where
    {-# INLINE fmap #-}
    fmap = map

まんまだ。
リスト型 [] は、fmap を満たす map を持っているのでファンクターである。

ピッコロ「孫、手をぬきやがって、オレはまじめに修行して新開発したってのによ」

Haskell
-- ピッコロ型と型生成関数の宣言
-- 'deriving (Show)' で「うまいこと文字列で表現してくれ」と頼んでいる
data Piccolo a = Piccolo a deriving (Show)

-- ピッコロ型はファンクターである
instance Functor Piccolo where
  -- fmap 実装('g' は任意関数を表す)
  fmap g (Piccolo x) = Piccolo (g x)

-- 必殺技のアルゴリズム(値を適当倍する)
cannon :: Float -> Float
cannon = (* 4.131)

ピッコロ「fmap 貫光殺砲うけてみろ!」

ghci
> fmap cannon (Piccolo 322)
Piccolo 1330.182

ラディッツ「くそ!こいつら戦闘力を自在に操りやがる!!」

関数型言語を知らない人も昨今は map の有り難みを知っている。
「配列とかリストとかの要素に、一気に処理をかけるやつだよね」

そう、ファンクターとは、それを更に抽象化したものだ。
配列とかリストやらの並びに制限されない map
すなわち fmap に適用できるものだ。

この「○○に制限されない」の○○を「文脈(コンテキスト)」と言う。
上記に示した :info Functorinstance は文脈をも表している。

Either は「どちらか」を、
[] は「並び」を、
Maybe は「無いか有るか」を、
IO は「入出力」を、
(->) は「関数」を、
(,) は「組」を。

圏論の Functor ではなく、
Mappable と命名した方が分かりやすいのでは?という意見があるらしい。

再びアプリカティブとは?

ナッパ「あいつナメック星人だぜ」
ピッコロ「オレはナメック星人だったのか・・・」

Haskell
data Namekian a = Namekian a deriving (Show)

instance Functor Namekian where
  fmap g (Namekian x) = Namekian (g x)

cannon :: Float -> Float
cannon = (* 4.131)

ネイル「わ・・・わたしと同化しろ・・・」
ピッコロ「なに!?きさまと!?」

Haskell
class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b -- '<*>' は2引数の中置関数を表す

instance Applicative Namekian where
  pure = Namekian
  Namekian g <*> namekian = fmap g namekian

-- 同化のアルゴリズム(てきとー)
assimilate :: Float -> Float -> Float
assimilate m n = m * n / 1000

ネイル「お・・・おまえのその力が数倍にもなる・・・」

ghci
> piccolo = Namekian 41310
> nail = Namekian 42000
> newPiccolo = pure assimilate <*> piccolo <*> nail
> newPiccolo
Namekian 1735020.0
> fmap cannon newPiccolo
Namekian 7167367.5

ピッコロ「こっ これが同化というやつなのか・・・!!」

ファンクターでは無理だった2引数関数を扱えるようになった。
それだけではない。一気に神様とだって融合できるのだ。

Haskell
assimilate3 :: Float -> Float -> Float -> Float
assimilate3 x y z = x * y * z / 1000
ghci
> kami = Namekian 220
> pure assimilate3 <*> piccolo <*> nail <*> kami
Namekian 3.8170442e8 -- もうよくわかんね

アプリカティブとは、関数のアリティをも制限しないファンクターなのである。
そういうことから Applicative ではなく、
Zippable の方が分かりやすいのでは?という意見があるらしい。

だが、これで終わっては次段階のモナドを見誤る。
Haskell の関数に sequenceA というものがある。
実際の定義よりもシンプルに下記に示す。

Haskell
sequenceA :: Applicative f => [f a] -> f [a]
-- 空リストならば、そのまま文脈に包む
sequenceA []     = pure []
-- リストの要素を、文脈に包まれたリストに再帰的に詰め込む
-- ':' は2引数の中置関数(コンシング/詰め込みを表す)
sequenceA (x:xs) = pure (:) <*> x <*> sequenceA xs -- 再帰定義

ピッコロはよく自らを増殖(分裂)して戦ったものだ。

ghci
> piccolo = Namekian 322
> replicate 3 piccolo
[Namekian 322,Namekian 322,Namekian 322]
> sequenceA (replicate 3 piccolo)
Namekian [322,322,322]

replicate は単に値を複製しているが、
sequenceA はそこから文脈を取り出し外側に置く。
言い換えれば、文脈(コンテキスト)を伴った反復処理を実現している。

つまりモナドとは?!

「プログラミングの基礎」として出てくる言葉「逐次、分岐、反復」。
乱暴に言えば、この3つの機能を備えたものはチューリング完全となる。

アプリカティブまでによって逐次・反復は実現できた。
(逐次:Applicative、反復:Functor
その拡張版がモナドなのである。
何を求め拡張するのか?
残された「分岐」である。

そう、モナドはチューリング完全を実現できるのである。
この点に関して IO がどうとか、do がどうとかの話は必要がない。
プログラマの立場からしたら、まずはこの話だけで十分ではないか?
○○モナドはこんなに便利なんだと先に聞かされても気持ちが悪い。
真っ先に知りたいのは「モナドが何者であるか」だ。

Monad の別名も提案があがっている。
Chainable, Joinable, FlatMappable

モナドの定義など見なくとも納得がいく。

連鎖できるのだ。
繋げられるのだ。
平坦に写像できるのだ。

そう、モナドならね。

なにを?
文脈を伴った計算を。

なぜ?
チューリング完全だから。

モナドはチューリング完全な Haskell の内部 DSL だ。
しかも(やはり)それはファースト・クラスなのである。
おお、すごい。

という理解でよいのでしょうか?

4
3
0

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
4
3